#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 #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 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.zpos.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.zpush |= 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.xpos.y>max.y || act->pos.yflags & 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 templates; std::vector 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 getByCategory(const char *category) { std::string catname = category; std::vector 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 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; iflags = 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 * 4; 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; } } } }