PHP TibiaCam file reader

Simple reader for uncompressed TibiaCam files (binary files, up to 60MB).

It’s also example of fast reading binary data from file and handling it as stream.

<?php

class BinaryData
{
    /** @var resource */
    private $data;
    /** @var int */
    private $length;

    /**
     * BinaryData constructor.
     * @param string $data
     */
    public function __construct($data)
    {
        $this->data = fopen('php://memory', 'wb+');
        fwrite($this->data, $data);
        $this->length = ftell($this->data);
        rewind($this->data);
    }

    /**
     * @return int
     */
    public function getLength()
    {
        return $this->length;
    }

    /**
     * @return bool
     */
    public function isEof()
    {
        return ftell($this->data) == $this->length;
    }

    /**
     * @param int $length
     */
    public function skip($length)
    {
        fseek($this->data, $length, SEEK_CUR);
    }

    /**
     * @return int
     */
    public function getU8()
    {
        return unpack('C', fread($this->data, 1))[1];
    }

    /**
     * @return int
     */
    public function getU16()
    {
        return unpack('v', fread($this->data, 2))[1];
    }

    /**
     * @return int
     */
    public function getU32()
    {
        return unpack('V', fread($this->data, 4))[1];
    }

    /**
     * @return int
     */
    public function getU64()
    {
        return unpack('P', fread($this->data, 8))[1];
    }

    /**
     * @param int $length
     * @return string
     */
    public function getString($length = -1)
    {
        if ($length == -1) {
            $length = $this->getU16();
        }

        return fread($this->data, $length);
    }

}

class NetworkPacket extends BinaryData
{
    /**
     * 0x01 = packets sent to player
     * 0x03 = packets received from player
     * 0x05 = refresh packets for fast cam rewinding
     */
    const TYPE_OUT = 0x01;
    const TYPE_IN = 0x03;
    const TYPE_REFRESH = 0x05;

    /** @var int */
    private $type;

    /**
     * NetworkPacket constructor.
     * @param string $data
     */
    public function __construct($data)
    {
        parent::__construct($data);
        $this->type = $this->getU8();
    }

    /**
     * @return int
     */
    public function getType()
    {
        return $this->type;
    }

}

class Cam extends BinaryData
{
    /**
     * Cam constructor.
     * @param string $filePath path to cam file
     */
    public function __construct($filePath)
    {
        parent::__construct(file_get_contents($filePath));
    }

    /**
     * @return array
     */
    public function readTibiaCamAttributes()
    {
        $this->skip(8); // text: TIBIACAM

        $camVersion = $this->getU8();
        $playerId = $this->getU32();
        $playerName = $this->getString();
        $protocolVersion = $this->getU16();
        $timeCreated = $this->getU64();

        return [$camVersion, $playerId, $playerName, $protocolVersion, $timeCreated];
    }

}

$fileName = 'nowy.cam.ready';
$cam = new Cam($fileName);
$camAttributes = $cam->readTibiaCamAttributes();
var_dump($camAttributes);

while (!$cam->isEof()) {
    $packetTime = $cam->getU64();
    var_dump($packetTime);
    $packet = new NetworkPacket($cam->getString());
    var_dump($packet->getType());
    var_dump($packet->getLength());
}

OTS Restarter script

restart.sh with console logs with timestamp and binary file copy (for GDB)

#!/bin/bash
ulimit -c unlimited
while true; do

 cp kasteria bins/kasteria_`date '+%Y-%m-%d_%H-%M'`_`crc32 kasteria`
 stdbuf -o 0 ./kasteria 2>&1 | ts '%Y-%m-%d_%H-%M-%.S' | tee -a 'console/console_'`date '+%Y-%m-%d'`'.log';

 echo START SLEEP FOR 3 SECONDS, PRESS CTRL+C TO TURN OFF RESTARTER
 sleep 3
 echo END SLEEP

done

Autowalk/cavebot for OTClient

Someone asked me about script that will make character run to given position and after reaching it run to another. I made it for free, so I decided to publish it as it may be interesting for others.

It’s not ready-to-run module, but I think it’s very close. For tests I pasted parts of it to ‘battle’ module and it worked.

This code requires ‘BOT_PROTECTION’ disabled in OTClient.

local walkEvent = nil
local positionsList = {
    { x = 32608, y = 32131, z = 7 },
    { x = 32601, y = 32131, z = 7 },
    { x = 32601, y = 32138, z = 7 },
    { x = 32608, y = 32138, z = 7 },
}

local isAttacking = 0
local isFollowing = 0
local currentTargetPositionId = 1

local autowalkTargetPosition = positionsList[currentTargetPositionId]

function init()
    connect(LocalPlayer, {
        onPositionChange = onCreaturePositionChange
    })

    connect(g_game, {
        onAttackingCreatureChange = onAttack,
        onFollowingCreatureChange = onFollow,
        onDisappear = onCreatureDisappear
    })
end

function terminate()
    disconnect(LocalPlayer, {
        onPositionChange = onCreaturePositionChange
    })

    disconnect(g_game, {
        onAttackingCreatureChange = onAttack,
        onFollowingCreatureChange = onFollow,
        onDisappear = onCreatureDisappear
    })
end

function walkToTarget()
    if not g_game.isOnline() then
        walkEvent = scheduleEvent(walkToTarget, 500)
        return
    end

    if g_game.getLocalPlayer():getStepTicksLeft() > 0 then
        -- wait until walk animation finish
        walkEvent = scheduleEvent(walkToTarget, g_game.getLocalPlayer():getStepTicksLeft())
        return
    end

    if isAttacking or isFollowing or not autowalkTargetPosition then
        -- do not walk with selected attack/follow target or when there is no target position
        walkEvent = scheduleEvent(walkToTarget, 100)
        return
    end

    -- fast search path on minimap (known tiles)
    steps, result = g_map.findPath(g_game.getLocalPlayer():getPosition(), autowalkTargetPosition, 5000, 0)
    if result == PathFindResults.Ok then
        g_game.walk(steps[1], true)
    elseif result == PathFindResults.Position then
        -- on target position
        currentTargetPositionId = currentTargetPositionId + 1
        autowalkTargetPosition = positionsList[(currentTargetPositionId % #positionsList) + 1]
    else
        -- slow search path on minimap, if not found, start 'scanning' map
        steps, result = g_map.findPath(g_game.getLocalPlayer():getPosition(), autowalkTargetPosition, 25000, 1)
        if result == PathFindResults.Ok then
            g_game.walk(steps[1], true)
        end
    end

    -- limit steps to 10 per second (100 ms between steps)
    walkEvent = scheduleEvent(walkToTarget, math.max(100, g_game.getLocalPlayer():getStepTicksLeft()))
end

function onCreaturePositionChange(creature, newPos, oldPos)
    if not walkEvent then
        -- start automoving 1 second after logging into game
        walkEvent = scheduleEvent(walkToTarget, 1000)
    end
end

function onAttack(creature)
    isAttacking = creature and creature:getId() or 0
end

function onFollow(creature)
    isFollowing = creature and creature:getId() or 0
end

function onCreatureDisappear(creature)
    if isAttacking == creature:getId() then
        isAttacking = 0
    end
    if isFollowing == creature:getId() then
        isFollowing = 0
    end
end

Eloth start scripts for GMs

ROOK

Create fast monster spawn

Spawn “Snake” every second (1000 ms) for 5 seconds:

!lua function fms(m, p, c) doSummonCreature(m, p, true, true) if (c > 1) then addEvent(fms, 1000, m, p, c-1) end end fms("Spider", getCreaturePosition(cid), 5)

Mass spawn around GOD:

!lua for i = 1, 20 do doSummonCreature("Spider", getCreaturePosition(cid), true, true) end

Spawn temple NPC

For FACC towns:

/s The Oracle

For PACC towns:

/s The Gatekeeper

MAIN

Open Desert Quest

Create teleports that allow 20+ levels do quest solo without items:

!lua doCreateTeleport(1387, Position(32672, 32102, 8), Position(32647, 32092, 7)) doCreateTeleport(1387, Position(32672, 32070, 8), Position(32673, 32089, 8))

Generate big depot

LUA code to generate backpack full of backpacks:

function fill(container, level)
	for i = 1, 20 do
		local innerContainer = doAddContainerItem(container, 2000)
		if level > 0 then
			fill(innerContainer, level - 1)
		end
	end
end
container = doCreateItem(2000, getThingPos(cid)) fill(container, 2)

One-liner for !lua debug talkaction:

function fill(c, l) for i = 1, 20 do ic = doAddContainerItem(c, 2000) if l > 0 then fill(ic, l -1) end end end c = doCreateItem(2000, getThingPos(cid)) fill(c, 2)

Then put it in parcel and send to depot.