#include "actor.h"
#include "jdraw.h"
#include "jinput.h"
#include "jfile.h"
#include "jutil.h"
#include "jaudio.h"
#include "room.h"
#include "editor.h"
#include "debug.h"
#include "config.h"
#include <vector>
#include "m_game.h"
#include "controller.h"

namespace actor
{
    uint8_t anims[4][4] = {
        {0, 1, 0, 2},
        {0, 1, 2, 3},
        {0, 1, 2, 0},
        {0, 1, 0, 1}};

    actor_t *first = nullptr;
    actor_t *dirty = nullptr;
    actor_t *selected = nullptr;
    actor_t *picked = nullptr;
    static bool room_changed = false;
    int brilli_brilli = 0;
    draw::surface *brilli;

    int current_tag = 0;
    bool floating_editing = false;

    int walk_channel = -1;
    bool push_sound_already_playing = false;
    std::vector<actor_t*> purgatory;

    void resetTag()
    {
        current_tag = 0;
    }

    actor_t *getFirst()
    {
        return first;
    }

    actor_t *getLast()
    {
        if (!first)
            return nullptr;
        actor_t *last = first;
        while (last->next)
            last = last->next;
        return last;
    }

    actor_t *getSelected()
    {
        return selected;
    }

    actor_t *create(std::string name, vec3_t p, vec3_t s, std::string bmp, SDL_Rect r, SDL_Point o)
    {
        actor_t *act = createEmptyActor();
        strcpy(act->name, name.c_str());
        strcpy(act->bmp, bmp.c_str());
        act->pos = p;
        act->size = s;
        act->surface = draw::getSurface(bmp.c_str());
        act->bmp_rect = r;
        act->bmp_offset = o;
        act->alive = true;
        return act;
    }

    actor_t *createEmptyActor()
    {
        actor_t *act = (actor_t *)malloc(sizeof(actor_t));
        act->tag = current_tag++;
        act->pos = {0, 0, 0};
        act->size = {8, 8, 8};
        act->bmp_rect = {0, 0, 32, 32};
        act->bmp_offset = {0, 0};
        act->anim_cycle = act->orient = act->movement = 0;
        act->push = act->mov_push = PUSH_NONE;
        act->below = act->above = nullptr;
        act->prev = act->next = nullptr;
        act->anim_wait = act->anim_wait_count = 0;
        act->anim_frame = 0;
        act->react_mask = act->react_push = 0;
        act->flags = FLAG_NONE;
        act->template_category = 0;
        act->alive = true;

        return act;
    }

    actor_t *duplicate(actor_t *act)
    {
        actor_t *new_act = createEmptyActor();
        actor::templates::copy(new_act, act);
        // new_act->surface = draw::getSurface(new_act->bmp);
        new_act->prev = new_act->next = new_act->above = new_act->below = nullptr;
        return new_act;
    }

    actor_t *createFromTemplate(const char *name)
    {
        actor_t *templ = actor::templates::getByName(name);
        if (!templ)
            return nullptr;
        return duplicate(templ);
    }

    actor_t *createFromFile(char **buffer)
    {
        if (*buffer)
        {
            actor_t *t = createEmptyActor();
            while (**buffer != 0)
            {
                const char *key = file::readString(buffer);

                if (util::strcomp(key, "name:"))
                {
                    const char *val = file::readString(buffer);
                    strcpy(t->name, val);
                }
                else if (util::strcomp(key, "bmp:"))
                {
                    const char *val = file::readString(buffer);
                    strcpy(t->bmp, val);
                    t->surface = draw::getSurface(t->bmp);
                }
                else if (util::strcomp(key, "bmp-rect:"))
                {
                    t->bmp_rect.x = file::readInt(buffer);
                    t->bmp_rect.y = file::readInt(buffer);
                    t->bmp_rect.w = file::readInt(buffer);
                    t->bmp_rect.h = file::readInt(buffer);
                }
                else if (util::strcomp(key, "bmp-offset:"))
                {
                    t->bmp_offset.x = file::readInt(buffer);
                    t->bmp_offset.y = file::readInt(buffer);
                }
                else if (util::strcomp(key, "pos:"))
                {
                    t->pos.x = file::readInt(buffer);
                    t->pos.y = file::readInt(buffer);
                    t->pos.z = file::readInt(buffer);
                }
                else if (util::strcomp(key, "size:"))
                {
                    t->size.x = file::readInt(buffer);
                    t->size.y = file::readInt(buffer);
                    t->size.z = file::readInt(buffer);
                }
                else if (util::strcomp(key, "orient:"))
                {
                    t->orient = util::stringToInt(file::readString(buffer), {"none", "xp", "xn", "yp", "yn", "zp", "zn"}, {0, 1, 2, 4, 8, 16, 32});
                }
                else if (util::strcomp(key, "movement:"))
                {
                    t->movement = util::stringToInt(file::readString(buffer), {"none", "x", "y", "z", "cw", "ccw", "rand", "randv", "hunt", "randj"}, {MOV_NONE, MOV_X, MOV_Y, MOV_Z, MOV_CW, MOV_CCW, MOV_RAND, MOV_RANDV, MOV_HUNT, MOV_RANDJ});
                }
                else if (util::strcomp(key, "anim-cycle:"))
                {
                    t->anim_cycle = util::stringToInt(file::readString(buffer), {"walk", "seq", "min", "duo"}, {0, 1, 2, 3});
                }
                else if (util::strcomp(key, "anim-wait:"))
                {
                    t->anim_wait = file::readInt(buffer);
                }
                else if (util::strcomp(key, "flags:"))
                {
                    const char *str = file::readString(buffer, true);
                    int value = 0;
                    while (str)
                    {
                        value |= util::stringToInt(str, {"none", "hero", "pushable", "reactive", "moving", "animated", "orientable", "deadly", "gravity", "pickable", "special", "noeditor", "inertia", "disappear"}, {FLAG_NONE, FLAG_HERO, FLAG_PUSHABLE, FLAG_REACTIVE, FLAG_MOVING, FLAG_ANIMATED, FLAG_ORIENTABLE, FLAG_DEADLY, FLAG_GRAVITY, FLAG_PICKABLE, FLAG_SPECIAL, FLAG_NOEDITOR, FLAG_INERTIA, FLAG_DISAPPEAR});
                        str = file::readString(buffer, true);
                    }
                    t->flags = value;
                }
                else if (util::strcomp(key, "react-push:"))
                {
                    const char *str = file::readString(buffer, true);
                    int value = 0;
                    while (str)
                    {
                        value |= util::stringToInt(str, {"none", "xp", "xn", "yp", "yn", "zp", "zn"}, {PUSH_NONE, PUSH_XP, PUSH_XN, PUSH_YP, PUSH_YN, PUSH_ZP, PUSH_ZN});
                        str = file::readString(buffer, true);
                    }
                    t->react_push = value;
                }
                else if (util::strcomp(key, "react-mask:"))
                {
                    const char *str = file::readString(buffer, true);
                    int value = 0;
                    while (str)
                    {
                        value |= util::stringToInt(str, {"none", "xp", "xn", "yp", "yn", "zp", "zn"}, {PUSH_NONE, PUSH_XP, PUSH_XN, PUSH_YP, PUSH_YN, PUSH_ZP, PUSH_ZN});
                        str = file::readString(buffer, true);
                    }
                    t->react_mask = value;
                }
                else if (util::strcomp(key, "}"))
                {
                    return t;
                }
            }
            if (t->flags & FLAG_MOVING)
                t->mov_push = t->orient;
            return t;
        }
        return nullptr;
    }

    actor_t *replaceWithTemplate(actor_t *act, const char *name)
    {
        if (!act)
            return nullptr;
        actor_t *newactor = createFromTemplate(name);
        newactor->pos.x = act->pos.x;
        newactor->pos.y = act->pos.y;
        newactor->pos.z = act->pos.z;
        if (act->prev)
            act->prev->next = newactor;
        if (act == first)
            first = newactor;
        if (act == dirty)
            dirty = newactor;
        if (act->next)
            act->next->prev = newactor;
        newactor->prev = act->prev;
        newactor->next = act->next;
        act->prev = act->next = nullptr;
        sendToPurgatory(act);
        return newactor;
    }

    char tmp[255];

    void cleanName(actor_t *act)
    {
        int size = strlen(act->name);
        if (act->name[size - 3] == '-' && act->name[size - 1] >= 48 && act->name[size - 1] <= 57 && act->name[size - 2] >= 48 && act->name[size - 2] <= 57)
        {
            act->name[size - 3] = 0;
        }
    }

    // Li donem al actor un nom únic
    void setUniqueName(actor_t *act)
    {
        // Recorrem tots els actors
        actor_t *other = first;
        while (other != nullptr)
        {
            // Si no som eixe actor...
            if (other != act)
            {
                // ... però tenim el mateix nom...
                if (strcmp(act->name, other->name) == 0)
                {
                    // Si el nom actual no acaba en guió + dos digits, li afegim "-01" al final
                    int size = strlen(act->name);
                    if (act->name[size - 3] != '-' || act->name[size - 1] < 48 || act->name[size - 1] > 57 || act->name[size - 2] < 48 || act->name[size - 2] > 57)
                    {
                        strcat(other->name, "-00");
                        strcat(act->name, "-01");
                    }
                    else
                    {
                        // Si ja acaba en dos digits, agafem el numero, li sumem 1, i li'l tornem a ficar
                        int num = (act->name[size - 1] - 48) + (act->name[size - 2] - 48) * 10;
                        num++;
                        act->name[size - 1] = (num % 10) + 48;
                        act->name[size - 2] = int(num / 10) + 48;
                    }
                    // I tornem a començar des del principi amb el nou nom
                    other = first;
                }
            }
            other = other->next;
        }
    }

    const char *numToOrient(uint8_t value)
    {
        tmp[0] = 0;
        if (value == 0)
            return "NONE";
        if (value & 1)
            strcat(tmp, "XP ");
        if (value & 2)
            strcat(tmp, "XN ");
        if (value & 4)
            strcat(tmp, "YP ");
        if (value & 8)
            strcat(tmp, "YN ");
        if (value & 16)
            strcat(tmp, "ZP ");
        if (value & 32)
            strcat(tmp, "ZN ");
        return tmp;
    }

    const char *numToFlags(uint16_t value)
    {
        tmp[0] = 0;
        if (value == 0)
            return "NONE";
        if (value & FLAG_PICKABLE)
            strcat(tmp, "PICKABLE ");
        if (value & FLAG_PUSHABLE)
            strcat(tmp, "PUSHABLE ");
        if (value & FLAG_REACTIVE)
            strcat(tmp, "REACTIVE ");
        if (value & FLAG_MOVING)
            strcat(tmp, "MOVING ");
        if (value & FLAG_ANIMATED)
            strcat(tmp, "ANIMATED ");
        if (value & FLAG_ORIENTABLE)
            strcat(tmp, "ORIENTABLE ");
        if (value & FLAG_DEADLY)
            strcat(tmp, "DEADLY ");
        if (value & FLAG_GRAVITY)
            strcat(tmp, "GRAVITY ");
        if (value & FLAG_SPECIAL)
            strcat(tmp, "SPECIAL ");
        if (value & FLAG_INERTIA)
            strcat(tmp, "INERTIA ");
        if (value & FLAG_DISAPPEAR)
            strcat(tmp, "DISAPPEAR ");
        return tmp;
    }

    const char *numToMov(uint8_t value)
    {
        if (value == 0)
            return "NONE";
        if (value == 1)
            return "X";
        if (value == 2)
            return "Y";
        if (value == 3)
            return "Z";
        if (value == 4)
            return "CW";
        if (value == 5)
            return "CCW";
        if (value == 6)
            return "RAND";
        if (value == 7)
            return "RANDV";
        if (value == 8)
            return "HUNT";
        if (value == 9)
            return "RANDJ";
        return "NONE";
    }

    actor_t *alpha = nullptr;
    const bool a_is_greater_than_b(actor_t *a, actor_t *b)
    {
        return strcmp(a->name, b->name) >= 0;
    }

    void order(actor_t *act)
    {
        if (!act)
            return;
        if (!a_is_greater_than_b(act, alpha))
        {
            act->next_alpha = alpha;
            alpha = act;
        }
        else
        {
            actor_t *other = alpha;
            actor_t *prev = nullptr;
            while (a_is_greater_than_b(act, other))
            {
                if (other->next_alpha)
                {
                    prev = other;
                    other = other->next_alpha;
                }
                else
                {
                    other->next_alpha = act;
                    return;
                }
            }
            prev->next_alpha = act;
            act->next_alpha = other;
        }
    }

    actor_t *alphaOrder(actor_t *act)
    {
        if (!act)
            return nullptr;
        alpha = act;
        act->next_alpha = nullptr;
        act = act->next;
        while (act)
        {
            act->next_alpha = nullptr;
            order(act);
            act = act->next;
        }
        return alpha;
    }

    void saveToFile(FILE *f, actor_t *act, bool tab)
    {
        char ws[5] = "";
        if (tab)
            strcpy(ws, "    ");
        fprintf(f, "\n%sactor{\n", ws);
        fprintf(f, "    %sname: %s\n", ws, act->name);
        fprintf(f, "    %sbmp: %s\n", ws, act->bmp);
        fprintf(f, "    %sbmp-rect: %i %i %i %i\n", ws, act->bmp_rect.x, act->bmp_rect.y, act->bmp_rect.w, act->bmp_rect.h);
        fprintf(f, "    %sbmp-offset: %i %i\n", ws, act->bmp_offset.x, act->bmp_offset.y);
        fprintf(f, "    %spos: %i %i %i\n", ws, act->pos.x, act->pos.y, act->pos.z);
        fprintf(f, "    %ssize: %i %i %i\n", ws, act->size.x, act->size.y, act->size.z);
        if (act->orient != 0)
            fprintf(f, "    %sorient: %s\n", ws, numToOrient(act->orient));
        if (act->anim_cycle != 0)
            fprintf(f, "    %sanim-cycle: %s\n", ws, act->anim_cycle == 0 ? "WALK" : act->anim_cycle == 1 ? "SEQ"
                                                                                 : act->anim_cycle == 2   ? "MIN"
                                                                                                          : "DUO");
        if (act->anim_wait != 0)
            fprintf(f, "    %sanim-wait: %i\n", ws, act->anim_wait);
        if (act->flags != 0)
            fprintf(f, "    %sflags: %s\n", ws, numToFlags(act->flags));
        if (act->react_mask != 0)
            fprintf(f, "    %sreact-mask: %s\n", ws, numToOrient(act->react_mask));
        if (act->react_push != 0)
            fprintf(f, "    %sreact-push: %s\n", ws, numToOrient(act->react_push));
        if (act->movement != 0)
            fprintf(f, "    %smovement: %s\n", ws, numToMov(act->movement));
        fprintf(f, "%s}\n", ws);
    }

    const bool check_2d_collision(actor_t *obj1, actor_t *obj2)
    {
        return (obj1->pos.x < obj2->pos.x + obj2->size.x) &&
               (obj1->pos.x + obj1->size.x > obj2->pos.x) &&
               (obj1->pos.y < obj2->pos.y + obj2->size.y) &&
               (obj1->pos.y + obj1->size.y > obj2->pos.y);
    }

    const bool does_collide(actor_t *obj1)
    {
        actor_t *obj2 = first;
        actor_t *hero = find("HERO");
        while (obj2)
        {
            if (obj2 != hero && obj1 != obj2 && (obj1->pos.z<obj2->pos.z+obj2->size.z) && check_2d_collision(obj1, obj2))
                return true;
            obj2 = obj2->next;
        }
        return false;
    }

    void find_non_colliding_position(actor_t *obj)
    {
        int dist = 1;
        while (does_collide(obj))
        {
            obj->pos.x += dist;
            if (!does_collide(obj)) return;
            obj->pos.x -= dist*2;
            if (!does_collide(obj)) return;
            obj->pos.x += dist;
            obj->pos.y += dist;
            if (!does_collide(obj)) return;
            obj->pos.y -= dist*2;
            if (!does_collide(obj)) return;
            obj->pos.y += dist;
            dist++;
        }
    }


    const bool is_above(actor_t *obj1, actor_t *obj2)
    {
        return check_2d_collision(obj1, obj2) && (obj1->pos.z == obj2->pos.z + obj2->size.z);
    }

    actor_t *any_above_me(actor_t *act)
    {
        actor_t *other = first;
        while (other)
        {
            if (is_above(other, act))
                return other;
            other = other->next;
            if (other == act)
                other = other->next;
        }
        /*
        other = dirty;
        while (other)
        {
            if (is_above(other, act)) return other;
            other = other->next;
            if (other == act) other = other->next;
        }
        */
        return nullptr;
    }

    actor_t *any_below_me(actor_t *act)
    {
        actor_t *other = first;
        while (other)
        {
            if (is_above(act, other))
                return other;
            other = other->next;
            if (other == act)
                other = other->next;
        }
        /*
        other = dirty;
        while (other)
        {
            if (is_above(act, other)) return other;
            other = other->next;
            if (other == act) other = other->next;
        }
        */
        return nullptr;
    }

    void setDirty(actor_t *act, const bool force)
    {
        if (!act->prev && !act->next && !(act == first))
        {
            act->next = first;
            if (first)
                first->prev = act;
            first = act;
        }
        /*
        if (act->prev==nullptr && act != first && !force) return;

        if (act->prev) act->prev->next = act->next;
        if (act->next) act->next->prev = act->prev;
        if (act == first) first = act->next;

        act->prev = nullptr;
        act->next = dirty;
        dirty = act;
        */
    }

    void select(actor_t *act)
    {
        selected = act;
    }

    const bool overlap(actor_t *act1, actor_t *act2)
    {
        return act1->inner_x < act2->inner_x + act2->bmp_rect.w &&
               act1->inner_x + act1->bmp_rect.w > act2->inner_x &&
               act1->inner_y > act2->inner_y + act2->bmp_rect.h &&
               act1->inner_y + act1->bmp_rect.h < act2->inner_y;
    }

    const bool isInFront(actor_t *act1, actor_t *act2)
    {
        return (act1->pos.x >= act2->pos.x + act2->size.x) ||
               (act1->pos.y >= act2->pos.y + act2->size.y) ||
               (act1->pos.z >= act2->pos.z + act2->size.z);
        /*
                if (act1->pos.x >= act2->pos.x+act2->size.x) { return true; }
                else if (act2->pos.x >= act1->pos.x+act1->size.x) { return false; }
                else if (act1->pos.y >= act2->pos.y+act2->size.y) { return true; }
                else if (act2->pos.y >= act1->pos.y+act1->size.y) { return false; }
                else if (act1->pos.z >= act2->pos.z+act2->size.z) { return true; }
                else if (act2->pos.z >= act1->pos.z+act1->size.z) { return false; }
                else { return false; }
        */
    }

    void reorder()
    {
        dirty = first;
        first = nullptr;

        while (dirty)
        {
            dirty->inner_x = 148 - dirty->bmp_offset.x + dirty->pos.x * 2 - dirty->pos.y * 2;
            dirty->inner_y = 91 - dirty->bmp_offset.y + dirty->pos.x + dirty->pos.y - dirty->pos.z * 2;

            if (first)
            {
                actor_t *current = first;
                while (true)
                {
                    if (overlap(dirty, current) || isInFront(dirty, current))
                    {
                        if (current->next)
                        {
                            current = current->next;
                        }
                        else
                        {
                            current->next = dirty;
                            dirty = dirty->next;
                            current->next->prev = current;
                            current->next->next = nullptr;
                            break;
                        }
                    }
                    else
                    {
                        dirty->prev = current->prev;
                        current->prev = dirty;
                        if (dirty->prev)
                            dirty->prev->next = dirty;
                        dirty = dirty->next;
                        current->prev->next = current;
                        if (current == first)
                            first = current->prev;
                        break;
                    }
                }
            }
            else
            {
                first = dirty;
                dirty = dirty->next;
                first->prev = first->next = nullptr;
            }
        }
    }

    uint8_t push(actor_t *source, actor_t *act, uint8_t push)
    {
        uint8_t result = 0;
        if ((act->flags & FLAG_PUSHABLE) && (!(source->flags & FLAG_HERO) || (hero::getSkills() & SKILL_GLOVES) || (push == PUSH_ZN)))
            act->push |= push;
        if ((act->flags & FLAG_REACTIVE) && (act->react_mask & push))
        {
            if (act->flags & FLAG_DEADLY)
                source->push |= PUSH_KILL;
            else
                result = act->react_push;
        }
        if ((source->flags & FLAG_HERO) && (act->flags & FLAG_DISAPPEAR))
        {
            audio::playSound("snd_disappear.wav", SOUND_BASIC);
            act = actor::replaceWithTemplate(act, "EXPLOSION");
            act->name[0] = '_';
            return PUSH_NONE;
        }

        if (source->flags & FLAG_DEADLY)
        {
            act->push |= PUSH_KILL;
        }

        if (source->flags & FLAG_HERO)
        {
            if (act->flags & FLAG_SPECIAL)
            {
                if (act->name[0] == 'B')
                { // Es un booster
                    hero::collectBooster(&act->name[5], (act->name[2] - 48) * 10 + (act->name[3] - 48));
                    audio::playSound("snd_boost.wav", SOUND_BASIC);
                }
                else if (act->name[0] == 'S')
                { // Es un skill
                    hero::giveSkill(&act->name[2]);
                    audio::playSound("snd_boost.wav", SOUND_BASIC);
                }
                else if (act->name[0] == 'P')
                {
                    if (hero::getSkills() & SKILL_BAG)
                    { // Es una part
                        hero::pickPart(&act->name[2]);
                        audio::playSound("snd_pick.wav", SOUND_BASIC);
                    }
                    else
                    {
                        audio::playSound("snd_push.wav", SOUND_BASIC);
                        return result;
                    }
                }
                else if (act->name[0] == 'A')
                {
                    hero::pickAnbernic(act->name);
                    audio::playSound("snd_pick.wav", SOUND_BASIC);
                }
                else if (act->name[0] == 'X')
                {
                    if (hero::isCarryingPrologoObject()) {
                        audio::playSound("snd_push.wav", SOUND_BASIC);
                        return result;
                    } else {
                        hero::pickPrologoObject(act->name[2]-48);
                        audio::playSound("snd_pick.wav", SOUND_BASIC);
                    }
                }
                else if (act->name[0] == 'Y')
                {
                    const int which = act->name[2]-48;
                    if (hero::getPrologoObjectState(which)==PROLOGO_OBJECT_PICKED)
                    {
                        hero::leavePrologoObject(which);
                        audio::playSound("snd_pick.wav", SOUND_BASIC);
                        act->name[0] = 'Z';
                        room::cycleColor(1);
                        return result;
                    } else {
                        audio::playSound("snd_push.wav", SOUND_BASIC);
                        return result;
                    }
                }
                else if (act->name[0] == 'Z')
                {
                    audio::playSound("snd_push.wav", SOUND_BASIC);
                    return result;
                }
                else
                {
                    SDL_assert(false);
                }
                act = actor::replaceWithTemplate(act, "EXPLOSION");
                act->name[0] = '_';
                room::cycleColor(1);
                return PUSH_NONE;
            }
        }
        return result;
    }

    void updateUserInput(actor_t *act)
    {
        vec3_t min = room::getMin();
        vec3_t max = room::getMax();

        bool moving = false;
        if (controller::down(KEY_LEFT)) // input::keyDown(SDL_SCANCODE_LEFT) || input::keyDown(config::getKey(KEY_LEFT)))
        {
            hero::useBoostRun();
            act->orient = PUSH_XN;
            if ((act->pos.x > min.x && act->pos.y >= min.y && (act->pos.y + act->size.y) <= max.y) || ((room::getDoors() & DOOR_XN) && (act->pos.y >= 24) && (act->pos.y <= 32)))
            {
                moving = true;
                // Si està en les vores d'una porta, espenta cap a centrar-lo
                if ((act->pos.x <= min.x) && (act->pos.z == room::getDoor(XN) * 4) && (room::getDoors() & DOOR_XN))
                {
                    if (act->pos.y < 28)
                        act->push |= PUSH_YP;
                    else if (act->pos.y > 28)
                        act->push |= PUSH_YN;
                    else
                    {
                        act->push |= PUSH_XN;
                        if (hero::getBoostRun() > 0) act->push |= PUSH_DOUBLE;
                    }
                }
                else
                {
                    act->push |= PUSH_XN;
                    if (hero::getBoostRun() > 0) act->push |= PUSH_DOUBLE;
                }
            }
        }
        else if (controller::down(KEY_RIGHT)) //(input::keyDown(SDL_SCANCODE_RIGHT) || input::keyDown(config::getKey(KEY_RIGHT)))
        {
            hero::useBoostRun();
            act->orient = PUSH_XP;
            if (((act->pos.x + act->size.x) < max.x && act->pos.y >= min.y && (act->pos.y + act->size.y) <= max.y) || ((room::getDoors() & DOOR_XP) && (act->pos.y >= 24) && (act->pos.y <= 32)))
            {
                moving = true;
                // Si està en les vores d'una porta, espenta cap a centrar-lo
                if (((act->pos.x + act->size.x) >= max.x) && (act->pos.z == room::getDoor(XP) * 4) && (room::getDoors() & DOOR_XP))
                {
                    if (act->pos.y < 28)
                        act->push |= PUSH_YP;
                    else if (act->pos.y > 28)
                        act->push |= PUSH_YN;
                    else
                    {
                        act->push |= PUSH_XP;
                        if (hero::getBoostRun() > 0) act->push |= PUSH_DOUBLE;
                    }
                }
                else
                {
                    act->push |= PUSH_XP;
                    if (hero::getBoostRun() > 0) act->push |= PUSH_DOUBLE;
                }
            }
        }
        else if (controller::down(KEY_UP)) // input::keyDown(SDL_SCANCODE_UP) || input::keyDown(config::getKey(KEY_UP)))
        {
            hero::useBoostRun();
            act->orient = PUSH_YN;
            if ((act->pos.y > min.y && act->pos.x >= min.x && (act->pos.x + act->size.x) <= max.x) || ((room::getDoors() & DOOR_YN) && (act->pos.x >= 24) && (act->pos.x <= 32)))
            {
                moving = true;
                // Si està en les vores d'una porta, espenta cap a centrar-lo
                if ((act->pos.y <= min.y) && (act->pos.z == room::getDoor(YN) * 4) && (room::getDoors() & DOOR_YN))
                {
                    if (act->pos.x < 28)
                        act->push |= PUSH_XP;
                    else if (act->pos.x > 28)
                        act->push |= PUSH_XN;
                    else
                    {
                        act->push |= PUSH_YN;
                        if (hero::getBoostRun() > 0) act->push |= PUSH_DOUBLE;
                    }
                }
                else
                {
                    act->push |= PUSH_YN;
                    if (hero::getBoostRun() > 0) act->push |= PUSH_DOUBLE;
                }
            }
        }
        else if (controller::down(KEY_DOWN)) // input::keyDown(SDL_SCANCODE_DOWN) || input::keyDown(config::getKey(KEY_DOWN)))
        {
            hero::useBoostRun();
            act->orient = PUSH_YP;
            if (((act->pos.y + act->size.y) < max.y && act->pos.x >= min.x && (act->pos.x + act->size.x) <= max.x) || ((room::getDoors() & DOOR_YP) && (act->pos.x >= 24) && (act->pos.x <= 32)))
            {
                moving = true;
                // Si està en les vores d'una porta, espenta cap a centrar-lo
                if (((act->pos.y + act->size.y) >= max.y) && (act->pos.z == room::getDoor(YP) * 4) && (room::getDoors() & DOOR_YP))
                {
                    if (act->pos.x < 28)
                        act->push |= PUSH_XP;
                    else if (act->pos.x > 28)
                        act->push |= PUSH_XN;
                    else
                    {
                        act->push |= PUSH_YP;
                        if (hero::getBoostRun() > 0) act->push |= PUSH_DOUBLE;
                    }
                }
                else
                {
                    act->push |= PUSH_YP;
                    if (hero::getBoostRun() > 0) act->push |= PUSH_DOUBLE;
                }
            }
        }
        // if ((input::keyPressed(SDL_SCANCODE_RETURN) || input::keyPressed(config::getKey(KEY_PICK))) && (hero::getSkills() & SKILL_PANTS))
        if ((controller::pressed(KEY_PICK)) && (hero::getSkills() & SKILL_PANTS))
        {
            if (picked)
            {
                picked->pos.x = act->pos.x;
                if (picked->pos.x + picked->size.x > room::getMax().x)
                    picked->pos.x = room::getMax().x - picked->size.x;
                picked->pos.y = act->pos.y;
                if (picked->pos.y + picked->size.y > room::getMax().y)
                    picked->pos.y = room::getMax().y - picked->size.y;
                picked->pos.z = act->pos.z;
                find_non_colliding_position(picked);
                /*if (does_collide(picked))
                {
                    picked->pos.x -= 2;
                    if (does_collide(picked))
                    {
                        picked->pos.x += 2;
                        picked->pos.y -= 2;
                    }
                }*/
                act->pos.z += picked->size.z;
                actor::actor_t *above = act->above;
                while (above)
                {
                    above->pos.z += picked->size.z;
                    above = above->above;
                }
                if (act->below)
                {
                    act->below->above = picked;
                    picked->below = act->below;
                }
                act->below = picked;
                picked->above = act;
                picked->next = picked->prev = nullptr;
                actor::setDirty(picked, true);
                picked = nullptr;
            }
            else if (act->below && act->below->flags & FLAG_PICKABLE)
            {
                // const int height = act->below->size.z;
                pick(act->below);
                // if (!(( input::keyDown(SDL_SCANCODE_SPACE) || input::keyDown(config::getKey(KEY_JUMP)) ) && (hero::getSkills()&SKILL_SHOES) && (act->pos.y+act->size.y)<=max.y && act->pos.y>=min.y && (act->pos.x+act->size.x)<=max.x && act->pos.x>=min.x && act->react_mask==0 && (act->pos.z==0 || act->below)))
                //     act->pos.z -= height;
            }
        }


        
        actor::actor_t *future_below = any_below_me(act);
        // if ((input::keyDown(SDL_SCANCODE_SPACE) || input::keyDown(config::getKey(KEY_JUMP))) &&
        if ((controller::down(KEY_JUMP)) && ((hero::getSkills() & SKILL_SHOES) || actor::hero::isPrologo())
            && (act->pos.y + act->size.y) <= max.y && act->pos.y >= min.y && (act->pos.x + act->size.x) <= max.x && act->pos.x >= min.x && act->react_mask == 0 && ((act->pos.z == 0 && room::getFloor() != 11) || (act->below || future_below)))
        {
            audio::pauseChannel(walk_channel);
            audio::playSound("snd_jump.wav", SOUND_BASIC);
            // [RZC 01/10/2024] Hack per a que al aterrar sobre els que desapareixen puga botar sobre ells, i a més ells desapareguen
            if (!act->below && future_below && future_below->flags & FLAG_DISAPPEAR)
                actor::push(act, future_below, PUSH_ZN);

            // [RZC 14/05/2024] hack usant react_mask i react_push del heroi. Llegir més avall.
            act->react_mask = hero::getBoostJump() > 0 ? 2 : 1; // =1 estic botant (anant cap amunt)
            act->react_push = 8;                                // es el comptador de botant, seguirà pujant mentres siga > 0
            // act->react_push=hero::getBoostJump()>0?16:8; // es el comptador de botant, seguirà pujant mentres siga > 0
            act->flags &= uint8_t(~FLAG_GRAVITY);
            if (act->below)
            {
                act->below->above = nullptr;
                act->below = nullptr;
            }
            hero::useBoostJump();
        }
        // if (input::keyDown(SDL_SCANCODE_Z) && act->pos.z>0) { act->push |= PUSH_ZN; moving = true; }
        // if (input::keyDown(SDL_SCANCODE_A) && act->pos.z<max.z) { act->push |= PUSH_ZP; moving = true; }

        // [RZC 14/05/2024] Açò es un hack. estic usant react_mask i react_push del hero com a guarda
        //                  i contador per al bot. Supose que perque se suposa que el heroi no te raons
        //                  per a ser REACTIU. Però si arriba a vindre la guardia civil m'en pega més
        //                  que a un xixo.
        if (act->react_mask)
        {
            // Si topetem en una vora de l'habitació, s'acabat el bot
            if ((act->pos.x + act->size.x) > max.x || act->pos.x < min.x || (act->pos.y + act->size.y) > max.y || act->pos.y < min.y)
                act->react_push = 0;

            // Si encara està botant (react_push > 0)...
            if (act->react_push > 0)
            {
                const int vel = act->react_mask;
                if (!actor::any_above_me(act))
                    act->pos.z += vel; // seguim pujant
                act->react_push--;     // augmentem el comptador de bot
            }
            else // Si ja ha acabat de botar...
            {
                act->react_mask = 0;        // desactivem la guarda (react_mask=0)
                act->flags |= FLAG_GRAVITY; // i reactivem el flag de gravetat
            }
        }

        // Que faça l'animació nomes si al final realment s'ha menejat
        if (moving)
        {
            act->flags |= FLAG_ANIMATED;
            if ((config::getSoundMode() == SOUND_ALL) && act->react_mask == 0 && ((act->below) || (act->pos.z == 0)))
                audio::resumeChannel(walk_channel);
        }
        else
        {
            act->flags &= uint8_t(~FLAG_ANIMATED);
            audio::pauseChannel(walk_channel);
        }
    }

    void updateMoving(actor_t *act)
    {
        if (act->flags & FLAG_MOVING && act->mov_push == 0)
            act->mov_push = act->orient; // Per a activar-lo si no ho està
        act->push |= act->mov_push;
    }

    void changeMoving(actor_t *act, const bool silent = false)
    {
        if (!silent && !push_sound_already_playing)
        {
            audio::playSound("snd_push.wav", SOUND_BASIC);
            push_sound_already_playing = true;
        }
        switch (act->movement)
        {
        case MOV_X:
            act->mov_push = act->mov_push == PUSH_XP ? PUSH_XN : PUSH_XP;
            break;
        case MOV_Y:
            act->mov_push = act->mov_push == PUSH_YP ? PUSH_YN : PUSH_YP;
            break;
        case MOV_Z:
            act->mov_push = act->mov_push == PUSH_ZN ? PUSH_ZP : PUSH_ZN;
            break;
        case MOV_CW:
            switch (act->mov_push)
            {
            case PUSH_XP:
                act->mov_push = PUSH_YN;
                break;
            case PUSH_YN:
                act->mov_push = PUSH_XN;
                break;
            case PUSH_XN:
                act->mov_push = PUSH_YP;
                break;
            case PUSH_YP:
                act->mov_push = PUSH_XP;
                break;
            }
            break;
        case MOV_CCW:
            switch (act->mov_push)
            {
            case PUSH_XP:
                act->mov_push = PUSH_YP;
                break;
            case PUSH_YP:
                act->mov_push = PUSH_XN;
                break;
            case PUSH_XN:
                act->mov_push = PUSH_YN;
                break;
            case PUSH_YN:
                act->mov_push = PUSH_XP;
                break;
            }
            break;
        case MOV_RAND:
            act->react_push = (rand() % 32) + 8; // [RZC 26/09/2024] Hack usant react_push en el moviment RAND per a contar la distancia abans de canviar de direcció
            switch (rand() % 4)
            {
            case 0:
                act->mov_push = PUSH_YP;
                break;
            case 1:
                act->mov_push = PUSH_XN;
                break;
            case 2:
                act->mov_push = PUSH_YN;
                break;
            case 3:
                act->mov_push = PUSH_XP;
                break;
            }
            break;
        case MOV_RANDV:
        case MOV_RANDJ:
            switch (rand() % 8)
            {
            case 0:
                act->mov_push = PUSH_YP;
                break;
            case 1:
                act->mov_push = PUSH_XN;
                break;
            case 2:
                act->mov_push = PUSH_YN;
                break;
            case 3:
                act->mov_push = PUSH_XP;
                break;
            case 4:
                act->mov_push = PUSH_YP;
                act->mov_push |= PUSH_XN;
                break;
            case 5:
                act->mov_push = PUSH_XN;
                act->mov_push |= PUSH_YN;
                break;
            case 6:
                act->mov_push = PUSH_YN;
                act->mov_push |= PUSH_XP;
                break;
            case 7:
                act->mov_push = PUSH_XP;
                act->mov_push |= PUSH_YP;
                break;
            }
            break;
        case MOV_HUNT:
            actor_t *heroi = find("HERO");
            if (heroi)
            {
                int tombola[2];
                int tombola_pos = 0;
                if (heroi->pos.x < act->pos.x)
                    tombola[tombola_pos++] = PUSH_XN;
                if (heroi->pos.x > act->pos.x)
                    tombola[tombola_pos++] = PUSH_XP;
                if (heroi->pos.y < act->pos.y)
                    tombola[tombola_pos++] = PUSH_YN;
                if (heroi->pos.y > act->pos.y)
                    tombola[tombola_pos++] = PUSH_YP;
                act->mov_push = tombola[rand() % tombola_pos];
            }
            break;
        }
        if (act->flags & FLAG_ORIENTABLE)
            act->orient = act->mov_push;
    }

    void updatePush(actor_t *act)
    {
        vec3_t min = room::getMin();
        vec3_t max = room::getMax();

        if ((act->push & PUSH_KILL) && (act->flags & FLAG_HERO) && (!hero::hasBoostGod()))
        {
            if (!editor::isDevMode())
            {
                const int lives = hero::getLives() - 1;
                hero::setLives(lives);
                stats::loseLive();
            }
            // [TODO] If lives == 0 anar a la pantalla de game-over o cat's life
            audio::pauseChannel(walk_channel);
            audio::playSound("snd_dead.wav", SOUND_BASIC);
            actor_t *act = actor::find("HERO");
            act = actor::replaceWithTemplate(act, "EXPLOSION");
            actor_t *act2 = actor::createFromTemplate("EXPLOSION");
            act2->pos = act->pos;
            act2->pos.z += 8;
            act2->anim_frame = 2;
            act->anim_wait = act2->anim_wait = 1;
            actor_t *other = first;
            while (other)
            {
                other->flags &= ~FLAG_MOVING;
                other = other->next;
            }
            actor::setDirty(act2);
            room::cycleColor(4);
            return;
        }

        if ((act->flags & FLAG_HERO) && (hero::hasBoostGod())) act->push &= ~PUSH_KILL;

        // [RZC 26/09/2024] Hack usant react_push en el moviment RAND per a contar la distancia abans de canviar de direcció
        if (act->movement == MOV_RAND)
        {
            act->react_push--;
            if (act->react_push == 0)
                changeMoving(act, true);
        }

        // [RZC 26/09/2024] Hack usant react_push en les bambolles de café per al dz del moviment de anar pegant botets
        if (act->movement == MOV_RANDJ)
        {
            if (act->pos.z == 0)
                act->react_push = 1;
            if (act->pos.z >= 6)
                act->react_push = -1;
            act->pos.z += act->react_push;
        }

        //int vel = (act->flags & FLAG_HERO) && (hero::getBoostRun() > 0) ? 2 : 1;
        int vel = (act->flags & FLAG_HERO) && (act->push & PUSH_DOUBLE) ? 2 : 1;
        act->push &= ~ PUSH_DOUBLE;

        if (act->push & PUSH_ZP)
        {
            if (act->pos.z >= max.z)
            {
                if (act->flags & FLAG_MOVING)
                    changeMoving(act);

                if (((act->flags & FLAG_HERO) != 0) && (room::getExit(ZP) != -1))
                {
                    room::load(room::getExit(ZP));
                    act->pos.z = 4;
                    actor::setDirty(act);
                    hero::setFirstPos();
                    room_changed = true;
                    return;
                }
            }
            else
            {
                // Tractament especial del moviment cap amunt: Estem movent, directament,
                // TOTS els objectes des d'este cap amunt si son pushables, en compte de
                // enviar el flag de push. Ho vem així perque sino la gravetat els tiraria
                // avall. De totes formes [TODO] revisar.
                // Estem tenint en compte els MOVILS, pero no els REACT
                actor_t *now = act;
                do
                {
                    actor::actor_t *other = actor::any_above_me(now);
                    if (!other || (other->flags & FLAG_PUSHABLE))
                        now->pos.z++;
                    if ((now->pos.z >= max.z) && (now->flags & FLAG_HERO) != 0 && (room::getExit(ZP) != -1))
                    {
                        room::load(room::getExit(ZP));
                        now->pos.z = 4;
                        actor::setDirty(now);
                        hero::setFirstPos();
                        room_changed = true;
                        return;
                    }
                    now = other;
                    if (now && !(now->flags & FLAG_PUSHABLE))
                    {
                        if (act->flags & FLAG_MOVING)
                            changeMoving(act);
                        now = nullptr;
                    }
                } while (now);

                actor::setDirty(act);
            }
            act->push &= ~PUSH_ZP;
        }

        if (act->push & PUSH_XN)
        {
            act->pos.x -= vel;
            actor::actor_t *other = actor::get_collision(act);
            if (other || (act->pos.x < min.x && (!(room::getDoors() & DOOR_XN) || (act->pos.y != 28) || (act->pos.z != room::getDoor(XN) * 4) || !(act->flags & FLAG_HERO))))
            {
                if (other) {
                    uint8_t push_value = push(act, other, PUSH_XN);
                    if (push_value) other->push |= push_value;
                }
                act->pos.x += vel;
                if (act->flags & FLAG_MOVING)
                    changeMoving(act);
                if (act->flags & FLAG_INERTIA)
                    act->push = PUSH_NONE;
            }
            else
            {
                // Si tenim a algú damunt, el movem també
                if (act->above && act->above->flags & FLAG_PUSHABLE)
                {
                    push(act, act->above, PUSH_XN);
                }

                // Si ja havem atravesat la porta, ens movem a la porta de l'altra costat
                if (act->pos.x < min.x - 4)
                {
                    room::load(room::getExit(XN));
                    act->pos.x = room::getMax().x - 4;
                    act->pos.z = room::getDoor(XP) * 4;
                    hero::setFirstPos();
                    room_changed = true;
                }

                actor::setDirty(act);
            }
            if (!(act->flags & FLAG_INERTIA))
                act->push &= ~PUSH_XN;
        }

        if (act->push & PUSH_XP)
        {
            act->pos.x += vel;
            actor::actor_t *other = actor::get_collision(act);
            if (other || ((act->pos.x + act->size.x) > max.x && (!(room::getDoors() & DOOR_XP) || (act->pos.y != 28) || !(act->flags & FLAG_HERO))))
            {
                if (other) {
                    uint8_t push_value = push(act, other, PUSH_XP);
                    if (push_value) other->push |= push_value;
                }
                act->pos.x -= vel;
                if (act->flags & FLAG_MOVING)
                    changeMoving(act);
                if (act->flags & FLAG_INERTIA)
                    act->push = PUSH_NONE;
            }
            else
            {
                if (act->above && act->above->flags & FLAG_PUSHABLE)
                {
                    push(act, act->above, PUSH_XP);
                }

                if ((act->pos.x + act->size.x) > max.x + 4)
                {
                    const bool llevar_abad = room::getCurrent() == 63 && room::getExit(XP) == 62;
                    room::load(room::getExit(XP));
                    if (llevar_abad)
                    {
                        actor::sendToPurgatory(actor::find("ABAD"));
                    }
                    act->pos.x = room::getMin().x - 3;
                    act->pos.z = room::getDoor(XN) * 4;
                    hero::setFirstPos();
                    room_changed = true;
                }

                actor::setDirty(act);
            }
            if (!(act->flags & FLAG_INERTIA))
                act->push &= ~PUSH_XP;
        }

        if (act->push & PUSH_YN)
        {
            act->pos.y -= vel;
            actor::actor_t *other = actor::get_collision(act);
            if (other || (act->pos.y < min.y && (!(room::getDoors() & DOOR_YN) || (act->pos.x != 28) || (act->pos.z != room::getDoor(YN) * 4) || !(act->flags & FLAG_HERO))))
            {
                if (other) {
                    uint8_t push_value = push(act, other, PUSH_YN);
                    if (push_value) other->push |= push_value;
            }
                act->pos.y += vel;
                if (act->flags & FLAG_MOVING)
                    changeMoving(act);
                if (act->flags & FLAG_INERTIA)
                    act->push = PUSH_NONE;
            }
            else
            {
                if (act->above && act->above->flags & FLAG_PUSHABLE)
                {
                    push(act, act->above, PUSH_YN);
                }

                if (act->pos.y < min.y - 4)
                {
                    room::load(room::getExit(YN));
                    act->pos.y = room::getMax().y - 4;
                    act->pos.z = room::getDoor(YP) * 4;
                    hero::setFirstPos();
                    room_changed = true;
                }

                actor::setDirty(act);
            }
            if (!(act->flags & FLAG_INERTIA))
                act->push &= ~PUSH_YN;
        }

        if (act->push & PUSH_YP)
        {
            act->pos.y += vel;
            actor::actor_t *other = actor::get_collision(act);
            if (other || ((act->pos.y + act->size.y) > max.y && (!(room::getDoors() & DOOR_YP) || (act->pos.x != 28) || !(act->flags & FLAG_HERO))))
            {
                if (other) {
                    uint8_t push_value = push(act, other, PUSH_YP);
                    if (push_value) other->push |= push_value;
                }
                act->pos.y -= vel;
                if (act->flags & FLAG_MOVING)
                    changeMoving(act);
                if (act->flags & FLAG_INERTIA)
                    act->push = PUSH_NONE;
            }
            else
            {
                if (act->above && act->above->flags & FLAG_PUSHABLE)
                {
                    push(act, act->above, PUSH_YP);
                }

                if ((act->pos.y + act->size.y) > max.y + 4)
                {
                    room::load(room::getExit(YP));
                    act->pos.y = room::getMin().y - 3;
                    act->pos.z = room::getDoor(YN) * 4;
                    hero::setFirstPos();
                    room_changed = true;
                }

                actor::setDirty(act);
            }
            if (!(act->flags & FLAG_INERTIA))
                act->push &= ~PUSH_YP;
        }

        if (act->push & PUSH_ZN)
        {
            // Si estic sobre el piso, no faig res
            if (act->pos.z == 0)
            {
                if ((act->flags & FLAG_MOVING) && (act->movement == MOV_Z))
                    changeMoving(act);
                act->push &= ~PUSH_ZN;
                if ((act->flags & FLAG_HERO) && (room::getFloor() == 11))
                    act->push |= PUSH_KILL;
                if (((act->flags & FLAG_HERO) == 0) || (room::getExit(ZN) == -1))
                    return;

                room::load(room::getExit(ZN));
                act->pos.z = room::getMax().z;
                actor::setDirty(act);
                hero::setFirstPos();
                room_changed = true;
                return;
            }

            // Si tinc a algú baix...
            if (act->below)
            {
                // ...i encara està baix...
                if (is_above(act, act->below))
                {
                    // ...li pase a ell el push, neteje el meu flag, canvie direcció si pertoca i me ane
                    uint8_t push_value = push(act, act->below, PUSH_ZN); // [RZC 20/09/2024] Canvie "act->below->push" per "act->push". Se li deu passar la reacció al que la inicia
                    if (push_value) act->push |= push_value;
                    act->push &= ~PUSH_ZN;
                    if ((act->flags & FLAG_MOVING) && (act->movement == MOV_Z))
                        changeMoving(act);
                    return;
                }
                // ... pero si ja no està baix, el desasociem, i seguim el proces
                act->below->above = nullptr;
                act->below = nullptr;
            }

            // Busquem si hi ha algú nou baix de mi
            actor_t *below = any_below_me(act); // [TODO] Jo crec que açò ho pot fer el propi get_collision()

            // Si sí que hi ha...
            if (below)
            {
                // ...el asociem...
                act->below = below;
                below->above = act;
                // ... i li passem el push, netejem el meu flag i gonnem
                uint8_t push_value = push(act, act->below, PUSH_ZN); // [RZC 20/09/2024] Canvie "act->below->push" per "act->push". Se li deu passar la reacció al que la inicia
                if (push_value) act->push |= push_value;

                act->push &= ~PUSH_ZN;
                if ((act->flags & FLAG_MOVING) && (act->movement == MOV_Z))
                    changeMoving(act);
                return;
            }

            // Si estem dins d'una porta, no caiguem. [TODO] Revisar
            if (act->flags & FLAG_HERO && ((act->pos.x + act->size.x) > max.x || act->pos.x < min.x || (act->pos.y + act->size.y) > max.y || act->pos.y < min.y))
                return;

            // Si arribem fins ací, podem moure la posició
            act->push &= ~PUSH_ZN;
            act->pos.z--;
            actor::setDirty(act);
        }

        /*
                if (act->push & PUSH_ZN) {
                    act->pos.z--;
                    actor::actor_t *other = actor::get_collision(act);
                    if (other || act->pos.z<0 || act->pos.x>max.x || act->pos.x<min.x || act->pos.y>max.y || act->pos.y<min.y)
                    {
                        if (other && other->flags & FLAG_REACTIVE) other->push |= PUSH_ZN;
                        act->pos.z++;
                        if (act->flags & FLAG_MOVING) changeMoving(act);
                    }
                    else
                    {
                        actor::setDirty(act);
                    }
                }
        */
    }

    void update(actor_t *act, const bool update_all)
    {
        if (!act)
            return;

        if (act == first)
        {
            push_sound_already_playing = false;
            brilli_brilli = (brilli_brilli + 1) & 0x7;
        }

        actor_t *next = act->next;

        // Actualitzem el frame de l'animació (si no te el flag de animat, no afectarà per a res)
        if (act->anim_wait_count == act->anim_wait)
        {
            act->anim_frame = act->anim_frame + 1;
            if (act->anim_frame == 4)
            {
                if (act->name[0] == '_')
                {
                    actor::sendToPurgatory(act);
                    return;
                }
                else
                {
                    act->anim_frame = 0;
                }
            }
            if (act->anim_cycle == 2 && act->anim_frame == 3)
                act->anim_frame = 0;
            act->anim_wait_count = 0;
        }
        else
        {
            act->anim_wait_count++;
        }

        if (act->flags & FLAG_HERO) updateUserInput(act);
        if (act->flags & FLAG_MOVING)
        {
            if (act->movement == MOV_HUNT && ((act->pos.x & 7) == 0 || (act->pos.y & 7) == 0))
                changeMoving(act);
            updateMoving(act);
        }
        if (act->flags & FLAG_GRAVITY) act->push |= PUSH_ZN;

        updatePush(act);

        if (!room_changed && update_all && next)
            update(next);
        room_changed = false;
    }

    void updateEditor(actor_t *act, const bool update_all)
    {
        if (!act)
            return;

        actor_t *next = act->next;

        if (act->anim_wait_count == act->anim_wait)
        {
            act->anim_frame = (act->anim_frame + 1) % 4;
            if (act->anim_cycle == 2 && act->anim_frame == 3)
                act->anim_frame = 0;
            act->anim_wait_count = 0;
        }
        else
        {
            act->anim_wait_count++;
        }

        if (update_all && next)
            updateEditor(next);
    }

    void print(int x, int y, int num)
    {
        int digits = 0;
        bool sign = num < 0;
        num = SDL_abs(num);
        int n = num;
        while (n > 0)
        {
            n = n / 10;
            digits++;
        }
        if (sign)
            digits++;
        x = x + digits * 4;
        if (num == 0)
            draw::draw(x + 4, y, 5, 7, 0, 120);
        while (num > 0)
        {
            draw::draw(x, y, 5, 7, (num % 10) * 5, 120);
            num = num / 10;
            x = x - 4;
        }
        if (sign)
            draw::draw(x, y, 5, 7, 50, 120);
    }

    void drawAt(actor_t *act, const int x, const int y)
    {
        act->inner_x = x;
        act->inner_y = y;
        draw(act, false);
    }

    void draw(actor_t *act, const bool draw_all)
    {
        if (!act)
            return;

        if (!editor::isEditing() || ((act->flags & FLAG_HERO) == 0))
        {
            const int x = act->inner_x; // 148-act->bmp_offset.x + act->pos.x*2 - act->pos.y*2;
            const int y = act->inner_y; // 91-act->bmp_offset.y + act->pos.x + act->pos.y - act->pos.z*2;

            const bool flip = ((act->flags & FLAG_ORIENTABLE) && (act->orient == PUSH_XN || act->orient == PUSH_YP)) ? DRAW_FLIP_HORIZONTAL : DRAW_FLIP_NONE;
            const int oo = ((act->flags & FLAG_ORIENTABLE) && (act->orient == PUSH_XN || act->orient == PUSH_YN)) ? act->bmp_rect.h : 0;

            const int ao = (act->flags & FLAG_ANIMATED) ? anims[act->anim_cycle][act->anim_frame] * act->bmp_rect.w : 0;

            draw::pushSource();
            draw::setSource(act->surface);
            if (editor::isEditing() && (act == selected) && modules::game::getSection() == modules::game::SECTION_ACTOR)
                draw::swapcol(1, room::getColor(1)); // Si està seleccionat, que canvie de color
            draw::stencil::set(act->tag);
            if (!(act->flags & FLAG_SPECIAL) || !(act->name[0] == 'Y') || (brilli_brilli<4) )
                draw::draw(x, y, act->bmp_rect.w, act->bmp_rect.h, act->bmp_rect.x + ao, act->bmp_rect.y + oo, flip);
            draw::swapcol(1, room::getColor(0)); // Tornem al color per defecte
            if ((act->flags & FLAG_SPECIAL) && (act->name[0] != 'A') && (act->name[0] != 'Z'))
            {
                draw::setSource(brilli);
                const int dx = (act->bmp_rect.w - 22) >> 1;
                draw::draw(x + dx, y, 22, 24, brilli_brilli * 22, 96, DRAW_FLIP_NONE);
            }
            draw::popSource();

            if (debug::isEnabled(DEBUG_ACTOR_POS))
            {
                char tmp[100];
                draw::print(SDL_itoa(act->inner_x, tmp, 10), x + 9, y + 20, LIGHT + WHITE, BLACK);
                draw::print(SDL_itoa(act->inner_y, tmp, 10), x + 9, y + 26, LIGHT + WHITE, BLACK);
                draw::swapcol(1, room::getColor(0)); // Tornem al color per defecte
            }
        }
        if ((act->flags & FLAG_HERO) && debug::isEnabled(DEBUG_ACTOR_FLAGS))
        {
            char tmp[100];
            draw::print(SDL_itoa(act->flags, tmp, 2), 1, 1, LIGHT + WHITE, BLACK);
            draw::swapcol(1, room::getColor(0)); // Tornem al color per defecte
        }

        if (draw_all && act->next)
            draw(act->next);
    }

    actor_t *find(std::string name)
    {
        actor_t *act = first;
        while (act)
        {
            if (name == act->name)
            {
                return act;
            }
            act = act->next;
        }
        return nullptr;
    }

    actor_t *findByTag(const int tag)
    {
        actor_t *act = first;
        while (act)
        {
            if (tag == act->tag)
            {
                return act;
            }
            act = act->next;
        }
        return nullptr;
    }

    actor_t *find_at(const int x, const int y, const int z)
    {
        actor_t *act = first;
        while (act)
        {
            if (act->pos.x == x && act->pos.y == y && act->pos.z == z)
            {
                return act;
            }
        }
        return nullptr;
    }

    actor_t *get_collision(actor_t *act)
    {
        actor_t *other = first;
        while (other)
        {
            if (other != act)
            {
                if (check_collision(act, other))
                {
                    return other;
                }
            }
            other = other->next;
        }

        /*
        other = dirty;
        while (other)
        {
            if (other != act)
            {
                if (check_collision(act, other))
                {
                    return other;
                }
            }
            other = other->next;
        }
        */

        return nullptr;
    }

    const bool check_collision(actor_t *obj1, actor_t *obj2)
    {
        return (obj1->pos.x < obj2->pos.x + obj2->size.x) &&
               (obj1->pos.x + obj1->size.x > obj2->pos.x) &&
               (obj1->pos.y < obj2->pos.y + obj2->size.y) &&
               (obj1->pos.y + obj1->size.y > obj2->pos.y) &&
               (obj1->pos.z < obj2->pos.z + obj2->size.z) &&
               (obj1->pos.z + obj1->size.z > obj2->pos.z);
    }

    void remove(actor_t *act)
    {
        if (!act)
            return;
        if (act->prev)
            act->prev->next = act->next;
        if (act == first)
            first = act->next;
        if (act == dirty)
            dirty = act->next;
        if (act->next)
            act->next->prev = act->prev;
        // draw::freeSurface(act->surface);
        if (act == selected)
            selected = nullptr;
        if (act->below)
            act->below->above = nullptr;
        if (act->above)
            act->above->below = nullptr;
        free(act);
    }

    void sendToPurgatory(actor_t *act)
    {
        if (!act) return;
        if (act->prev) act->prev->next = act->next;
        if (act == first) first = act->next;
        if (act == dirty) dirty = act->next;
        if (act->next) act->next->prev = act->prev;
        if (act == selected) selected = nullptr;
        if (act->below) act->below->above = nullptr;
        if (act->above) act->above->below = nullptr;
        act->alive = false;
        purgatory.push_back(act);
    }
    
    void cleanPurgatory()
    {
        for (auto act : purgatory) remove(act);
        purgatory.clear();
    }

    void pick(actor_t *act)
    {
        if (!act)
            return;
        if (act->prev)
            act->prev->next = act->next;
        if (act == first)
            first = act->next;
        if (act == dirty)
            dirty = act->next;
        if (act->next)
            act->next->prev = act->prev;
        if (act == selected)
            selected = nullptr;
        picked = act;
        picked->pos.x = 24;
        picked->pos.y = 80;
        picked->pos.z = 0;
        picked->inner_x = 148 - act->bmp_offset.x + act->pos.x * 2 - act->pos.y * 2;
        picked->inner_y = 91 - act->bmp_offset.y + act->pos.x + act->pos.y - act->pos.z * 2;
    }

    actor_t *getPicked() { return picked; }

    void clear(const bool all)
    {
        resetTag();
        actor_t *hero = nullptr;
        actor_t *act = first;
        while (act)
        {
            actor_t *tmp = act->next;
            if (!all && (act->flags & FLAG_HERO))
            {
                hero = act;
            }
            else
            {
                free(act);
            }
            act = tmp;
        }
        if (picked)
        {
            free(picked);
            picked = nullptr;
        }
        first = dirty = nullptr;
        if (hero)
        {
            hero->above = hero->below = hero->next = hero->prev = nullptr;
            first = hero;
        }
        selected = nullptr;
    }

    void setFloatingEditing(const bool value)
    {
        floating_editing = value;
    }

    const bool getFloatingEditing()
    {
        return floating_editing;
    }
    void pauseWalkSound()
    {
        audio::pauseChannel(walk_channel);
    }

    namespace templates
    {
        std::vector<actor_t> templates;
        std::vector<std::string> categories;

        void load()
        {
            // newCategory("default");
            categories.clear();
            templates.clear();
            char filename[] = "templates.txt";
            int filesize = 0;
            char *buffer = file::getFileBuffer(filename, filesize, true);
            char *original_buffer = buffer;
            int current_category = 0;

            if (buffer)
            {
                while (*buffer != 0)
                {
                    const char *key = file::readString(&buffer);

                    if (util::strcomp(key, "category{"))
                    {
                        const char *key = file::readString(&buffer);
                        if (util::strcomp(key, "name:"))
                        {
                            const char *val = file::readString(&buffer);
                            current_category = newCategory(val);
                        }
                    }
                    else if (util::strcomp(key, "actor{"))
                    {
                        actor_t *t = createFromFile(&buffer);
                        t->template_category = current_category;
                        templates.push_back(*t);
                        free(t);
                    }
                    else if (util::strcomp(key, "}"))
                    {
                        current_category = 0;
                    }
                }
                free(original_buffer);
            }
        }

        void save()
        {
            FILE *f = fopen("data/templates.txt", "w");

            for (auto cat_name : categories)
            {

                fprintf(f, "\ncategory{\n");
                fprintf(f, "    name: %s\n", cat_name.c_str());

                auto actors = getByCategory(cat_name.c_str());
                for (auto t : actors)
                {
                    saveToFile(f, &t, true);
                }

                fprintf(f, "\n}\n");
            }

            /*for (int i=0; i<templates.size(); ++i)
            {
                actor_t t = templates[i];
                saveToFile(f, &t);
            }*/

            fclose(f);
        }

        /*const int size()
        {
            return templates.size();
        }*/

        std::vector<actor::actor_t> getByCategory(const char *category)
        {
            std::string catname = category;
            std::vector<actor_t> actors;
            for (auto act : templates)
            {
                if (act.template_category >= (int)categories.size())
                    act.template_category = 0;
                if (categories[act.template_category] == catname)
                    actors.push_back(act);
            }
            return actors;
        }

        std::vector<std::string> getCategories()
        {
            return categories;
        }

        const int newCategory(const char *name)
        {
            categories.push_back(name);
            return categories.size() - 1;
        }

        actor_t *get(const int index)
        {
            return &templates[index];
        }

        actor_t *getByName(const char *name)
        {
            for (int i = 0; i < (int)templates.size(); ++i)
                if (util::strcomp(name, templates[i].name))
                    return &templates[i];
            // for (auto t : templates) if (util::strcomp(name, t.name)) return &t;
            return nullptr;
        }

        void copy(actor_t *dest, actor_t *source)
        {
            strcpy(dest->name, source->name);
            strcpy(dest->bmp, source->bmp);
            dest->bmp_rect = source->bmp_rect;
            dest->bmp_offset = source->bmp_offset;
            dest->pos = source->pos;
            dest->size = source->size;
            dest->orient = source->orient;
            dest->anim_cycle = source->anim_cycle;
            dest->anim_wait = source->anim_wait;
            dest->flags = source->flags;
            dest->react_mask = source->react_mask;
            dest->react_push = source->react_push;
            dest->movement = source->movement;
            dest->template_category = source->template_category;
            dest->surface = source->surface;
        }

        void add(actor_t *act)
        {
            // Fem una copia del actor
            actor_t new_template;
            copy(&new_template, act);

            // Netejem el nom de numerets
            cleanName(&new_template);

            // Si ja hi ha una plantilla amb eixe nom...
            if (actor::templates::getByName(act->name))
            {
                // ... la actualitzem amb les dades del actor seleccionat
                actor_t *existing_template = actor::templates::getByName(act->name);
                new_template.template_category = existing_template->template_category; // Li fiquem la categoria que tenia abans
                copy(existing_template, &new_template);
            }
            else
            {
                // ... i sinó, afegim el actor seleccionat a la llista de plantilles
                new_template.template_category = 0; // Li fiquem la categoria per defecte
                templates.push_back(new_template);
            }
            save();
        }

        void swapCategories(const int a, const int b)
        {
            if (a >= (int)categories.size() || a < 0)
                return;
            if (b >= (int)categories.size() || b < 0)
                return;
            auto str = categories[a];
            categories[a] = categories[b];
            categories[b] = str;

            for (auto &actor : templates)
            {
                if (actor.template_category == a)
                    actor.template_category = b;
                else if (actor.template_category == b)
                    actor.template_category = a;
            }
            /*
                        auto old_categories = categories;
                        categories.clear();
                        std::string relocated = "";
                        for (int i=0; i<old_categories.size(); ++i)
                        {
                            if (i==index) {
                                relocated = old_categories[i];
                            } else {
                                categories.push_back(old_categories[i]);
                                if (relocated != "") { categories.push_back(relocated); relocated = ""; }
                            }
                        }
            */
        }
    }

    namespace hero
    {
        int lives = 8;
        int boost_jumps = 0;
        int boost_steps = 0;
        int boost_god = 0;
        int skills = SKILL_NONE;
        int parts = PART_NONE;
        bool boosters_collected[100];
        bool anbernics[10] = {false, false, false, false, false, false, false, false, false, false};
        vec3_t first_pos = {0, 0, 0};
        int first_orient = 0;
        bool dead = false;
        bool prologo = false;
        int prologo_objects[4] = {PROLOGO_OBJECT_INITIAL, PROLOGO_OBJECT_INITIAL, PROLOGO_OBJECT_INITIAL, PROLOGO_OBJECT_INITIAL};
        int num_prologo_objects=0;

        void setPrologo(const bool value)
        {
            prologo = value;
        }

        const bool isPrologo()
        {
            return prologo;
        }

        void init(const bool complete)
        {
            actor::actor_t *hero = nullptr;
            if (prologo)
            {
                hero = actor::create("HERO", {16, 32, 0}, {6, 6, 8}, "gat.gif", {0, 0, 24, 28}, {-4, 32});
            }
            else
            {
                hero = actor::create("HERO", {16, 32, 0}, {6, 6, 12}, "test.gif", {0, 32, 20, 32}, {-6, 38});
            }
            hero->flags = FLAG_HERO | FLAG_PUSHABLE | FLAG_GRAVITY | FLAG_ORIENTABLE | FLAG_ANIMATED;
            actor::setDirty(hero, true);

            boost_jumps = boost_steps = boost_god = 0;
            dead = false;

            if (complete)
            {
                stats::reset();
                lives = 8;
                skills = SKILL_NONE;
                if (prologo) skills &= SKILL_SHOES;
                for (int i=0;i<4;++i) prologo_objects[i] = PROLOGO_OBJECT_INITIAL;
                num_prologo_objects=0;
                parts = PART_NONE;
                for (int i = 0; i < 10; ++i)
                    anbernics[i] = false;
                for (int i = 0; i < 100; ++i)
                    boosters_collected[i] = false;
                brilli = draw::getSurface("objectes.gif");
                if ((config::getSoundMode() == SOUND_ALL))
                {
                    walk_channel = audio::playSound("snd_walk.wav", SOUND_ALL, -1);
                    audio::pauseChannel(walk_channel);
                }
            }
            else
            {
                hero->pos = first_pos;
                hero->orient = first_orient;
            }
        }

        int getLives()
        {
            return lives;
        }

        void setLives(int value)
        {
            lives = value;
        }

        void die()
        {
            dead = true;
        }

        bool isDead()
        {
            return dead;
        }
        const int getBoosterFromString(const char *booster)

        {
            static const char *boostset_name[4] = {"RUN", "GOD", "JUMP", "LIVE"};
            for (int i = 0; i < 4; ++i)
            {
                if (strcmp(booster, boostset_name[i]) == 0)
                {
                    return 1 << i;
                }
            }
            return 0;
        }
        const char *getBoosterName(int booster)
        {
            switch (booster)
            {
            case 1:
                return "RUN";
            case 2:
                return "GOD";
            case 4:
                return "JUMP";
            case 8:
                return "LIVE";
            default:
                return "";
            }
        }

        bool giveBooster(char *booster)
        {
            const int value = getBoosterFromString(booster);
            switch (value)
            {
            case BOOST_GOD:
                boost_god = 99 * 2;
                break;
            case BOOST_RUN:
                boost_steps = 99 * 2;
                break;
            case BOOST_JUMP:
                boost_jumps = 10;
                break;
            case BOOST_LIVE:
                lives++;
                break;
            }
            return value != 0;
        }

        void collectBooster(const char *booster, int id)
        {
            boosters_collected[id] = true;
            switch (getBoosterFromString(booster))
            {
            case BOOST_GOD:
                boost_god = 99 * 2;
                break;
            case BOOST_RUN:
                boost_steps = 99 * 2;
                break;
            case BOOST_JUMP:
                boost_jumps = 10;
                break;
            case BOOST_LIVE:
                lives++;
                break;
            }
        }

        bool wasBoosterCollected(int id)
        {
            return boosters_collected[id];
        }

        int getBoostGod()
        {
            return boost_god;
        }
        int getBoostRun()
        {
            return boost_steps;
        }

        int getBoostJump()
        {
            return boost_jumps;
        }

        void useBoostGod()
        {
            if (boost_god > 0)
                boost_god--;
        }

        void useBoostRun()
        {
            if (boost_steps > 0)
                boost_steps--;
        }

        void useBoostJump()
        {
            if (boost_jumps > 0)
                boost_jumps--;
        }

        const bool hasBoostGod()
        {
            return boost_god > 0;
        }

        const int getSkillFromString(char *skill)
        {
            static const char *skillset_name[4] = {"SHOES", "GLOVES", "PANTS", "BAG"};
            for (int i = 0; i < 4; ++i)
            {
                if (strcmp(skill, skillset_name[i]) == 0)
                {
                    return 1 << i;
                }
            }
            return 0;
        }

        const char *getSkillName(int skill)
        {
            switch (skill)
            {
            case 1:
                return "SHOES";
            case 2:
                return "GLOVES";
            case 4:
                return "PANTS";
            case 8:
                return "BAG";
            default:
                return "";
            }
        }

        bool giveSkill(int skill)
        {
            skills |= skill;
            return skill != 0;
        }

        bool giveSkill(char *skill)
        {
            const int skill_number = getSkillFromString(skill);
            if (skill_number==SKILL_SHOES) modules::game::setMissatge("  JA TENS LES SABATES!-   ARA JA POTS BOTAR!");
            else if (skill_number==SKILL_GLOVES) modules::game::setMissatge("  JA TENS ELS GUANTS!- ARA JA POTS ESPENTAR!");
            else if (skill_number==SKILL_PANTS) modules::game::setMissatge(" JA TENS ELS PANTALONS!- JA POTS AGAFAR COSES!");
            else if (skill_number==SKILL_BAG) modules::game::setMissatge("  JA TENS LA MOTXILLA!-  A ARREPLEGAR PECES!");
            return giveSkill(skill_number);
        }

        bool dropSkill(int skill)
        {
            skills &= ~skill;
            return skill != 0;
        }

        bool dropSkill(char *skill)
        {
            return dropSkill(getSkillFromString(skill));
        }

        bool wasSkillCollected(char *skill)
        {
            return skills & getSkillFromString(skill);
        }

        int getSkills()
        {
            return skills;
        }

        const int getPartFromString(char *part)
        {
            static const char *partset_name[6] = {"FILTER", "PUMP", "TIMER", "SALT", "PIPE", "ELBOW"};
            for (int i = 0; i < 6; ++i)
            {
                if (strcmp(part, partset_name[i]) == 0)
                {
                    return 1 << i;
                }
            }
            return PART_NONE;
        }

        const char *getPartName(int part)
        {
            switch (part)
            {
            case 1:
                return "FILTER";
            case 2:
                return "PUMP";
            case 4:
                return "TIMER";
            case 8:
                return "SALT";
            case 16:
                return "PIPE";
            case 32:
                return "ELBOW";
            default:
                return "";
            }
        }

        bool pickPart(char *part)
        {
            const int value = getPartFromString(part);
            parts |= value;
            if (value != 0) stats::collectPart();

            int num_parts = stats::getNumPartsCollected();
            char text[] = "   PECES ARREPLEGADES:-           0/6";
            text[34] = num_parts + 48;
            modules::game::setMissatge(text);

            return value != 0;
        }

        bool dropPart(char *part)
        {
            const int value = getPartFromString(part);
            parts &= ~value;
            return value != 0;
        }

        bool wasPartCollected(char *part)
        {
            return parts & getPartFromString(part);
        }

        int getParts()
        {
            return parts;
        }

        void pickAnbernic(char *name)
        {
            anbernics[name[8] - 48] = true;
            int num_anbernics = getNumAmbernicsCollected();
            if (num_anbernics==10)
            {
                modules::game::setMissatge("    HAS DESBLOQUEJAT-      EL PROLOGO!");
                config::setProgoloDesbloquejat();
            }
            else
            {
                char text[] = " ANBERNICS ARREPLEGADES:-           0/10";
                text[36] = num_anbernics+48;
                modules::game::setMissatge(text);
            }
        }

        bool wasAnbernicCollected(char *name)
        {
            return anbernics[name[8] - 48];
        }

        int getNumAmbernicsCollected()
        {
            int count = 0;
            for (auto anbernic : anbernics)
                if (anbernic)
                    count++;
            return count;
        }

        void pickPrologoObject(int which)
        {
            prologo_objects[which] = PROLOGO_OBJECT_PICKED;
        }

        void leavePrologoObject(int which)
        {
            prologo_objects[which] = PROLOGO_OBJECT_LEFT;
            num_prologo_objects++;
        }

        const bool isCarryingPrologoObject()
        {
            for (auto object : prologo_objects) if (object==PROLOGO_OBJECT_PICKED) return true;
            return false;
        }

        const int getPrologoObjectState(int which)
        {
            return prologo_objects[which];
        }

        const int getNumPrologoObjectsDone()
        {
            return num_prologo_objects;
        }

        void move(int *x, int *y, int *z)
        {
            actor_t *hero = actor::find("HERO");
            if (x)
                hero->pos.x = *x;
            if (y)
                hero->pos.y = *y;
            if (z)
                hero->pos.z = *z;
        }

        void setFirstPos()
        {
            actor_t *hero = actor::find("HERO");
            first_pos = hero->pos;
            first_orient = hero->orient;
        }

    }

    namespace stats
    {
        int partsCollected = 0;
        bool roomVisited[MAX_ROOMS];
        int livesLost = 0;
        int catsLifeOdds = 2;
        uint32_t start_time = 0;

        void reset()
        {
            partsCollected = livesLost = 0;
            catsLifeOdds = 2;
            for (int i = 0; i < MAX_ROOMS; ++i)
                roomVisited[i] = false;
            start_time = SDL_GetTicks();
        }

        void collectPart() { partsCollected++; }
        void visitRoom(int room) { roomVisited[room] = true; }
        void loseLive() { livesLost++; }

        int getNumPartsCollected() { return partsCollected; }
        int getRoomsVisited()
        {
            int roomsVisited = 0;
            for (int i = 0; i < MAX_ROOMS; ++i)
                if (roomVisited[i])
                    roomsVisited++;
            return roomsVisited;
        }

        uint32_t getStartTime() { return start_time; }

        int getLivesLost() { return livesLost; }

        bool catsLife()
        {
            if ((rand() % catsLifeOdds) == 0)
            {
                catsLifeOdds *= 2;
                return true;
            }
            else
            {
                return false;
            }
        }

    }

}
