Files
thepool/source/actor.cpp
Raimon Zamora baae73a38d - [FIX] Un café feia impossible passar per una habitació
- [FIX] No s'activaba el prólogo quan toca
- [FIX] El gràfic de les portes de vegades era transparent
- [FIX] Separades unes caixes que feien molt frustrant agafar una cosa
- [FIX] Quan tenia el café de doble velocitat no podia creuar portes
- [FIX] Algunes vegades el objecte dropejat encara feia clipping amb altres
- [NEW] Missatges en els moments importants
- [NEW] Quan te pases el joc també te dona els 'stats' de temps, vides i habitacions visitades.
2024-10-10 12:58:22 +02:00

2484 lines
81 KiB
C++

#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 = 5;
uint32_t start_time = 0;
void reset()
{
partsCollected = livesLost = 0;
catsLifeOdds = 5;
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;
}
}
}
}