#include #include #include "game.h" using namespace baselib; // --------- PHYSICS TWEAK! -------- const float ground_control_speed = 7.0f; const float ground_control_accel = 2.0f; const float ground_friction = 0.5f; const float ground_jump_speed = 12.0f; const float air_control_speed = 3.5f; const float air_control_accel = 1.2f; const float air_friction = 0.95f; const float hook_length = 32*10.0f; const float hook_fire_speed = 45.0f; const float hook_drag_accel = 3.0f; const float hook_drag_speed = 15.0f; const float gravity = 0.5f; class player* get_player(int index); void create_healthmod(vec2 p, int amount); void create_explosion(vec2 p, int owner = -1, bool bnodamage = false); void create_smoke(vec2 p); void create_sound(vec2 pos, int sound, int loopflags = 0); class player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, class entity* notthis = 0); // TODO: rewrite this smarter! void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity) { // do the move vec2 pos = *inout_pos; vec2 vel = *inout_vel; float distance = length(vel); int max = (int)distance; vec2 offsets[4] = { vec2(-size.x/2, -size.y/2), vec2( size.x/2, -size.y/2), vec2(-size.x/2, size.y/2), vec2( size.x/2, size.y/2)}; if(distance > 0.00001f) { vec2 old_pos = pos; for(int i = 0; i <= max; i++) { float amount = i/(float)max; if(max == 0) amount = 0; vec2 new_pos = pos + vel*amount; // TODO: this row is not nice for(int p = 0; p < 4; p++) { vec2 np = new_pos+offsets[p]; vec2 op = old_pos+offsets[p]; if(col_check_point(np)) { int affected = 0; if(col_check_point(np.x, op.y)) { vel.x = -vel.x*elasticity; pos.x = old_pos.x; new_pos.x = old_pos.x; affected++; } if(col_check_point(op.x, np.y)) { vel.y = -vel.y*elasticity; pos.y = old_pos.y; new_pos.y = old_pos.y; affected++; } if(!affected) { new_pos = old_pos; pos = old_pos; vel *= -elasticity; } } } old_pos = new_pos; } pos = old_pos; } *inout_pos = pos; *inout_vel = vel; } // TODO: rewrite this smarter! bool intersect_line(vec2 pos0, vec2 pos1, vec2 *out) { float d = distance(pos0, pos1); for(float f = 0; f < d; f++) { float a = f/d; vec2 pos = mix(pos0, pos1, a); if(col_check_point(pos)) { if(out) *out = pos; return true; } } if(out) *out = pos1; return false; } // class event_handler { static const int MAX_EVENTS = 128; static const int MAX_DATASIZE = 128*4; int types[MAX_EVENTS]; // TODO: remove some of these arrays int offsets[MAX_EVENTS]; int sizes[MAX_EVENTS]; char data[MAX_DATASIZE]; int current_offset; int num_events; public: event_handler() { num_events = 0; } void *create(int type, int size) { void *p = &data[current_offset]; offsets[num_events] = current_offset; types[num_events] = type; sizes[num_events] = size; current_offset += size; num_events++; return p; } void clear() { num_events = 0; current_offset = 0; } void snap(int snapping_client) { for(int i = 0; i < num_events; i++) { void *d = snap_new_item(types[i], i, sizes[i]); mem_copy(d, &data[offsets[i]], sizes[i]); } } }; static event_handler events; /* template class pool { struct element { int next_free; T data; }; element elements[SIZE]; int first_free; public: pool() { first_free = 0; for(int i = 0; i < SIZE; i++) elements[i].next_free = i+1; elements[SIZE-1].next_free = -1; } };*/ // a basic entity class entity { private: friend class game_world; friend class player; entity *prev_entity; entity *next_entity; int index; public: vec2 pos; float proximity_radius; unsigned flags; int objtype; enum { FLAG_DESTROY=0x00000001, }; entity(int objtype) { this->objtype = objtype; pos = vec2(0,0); flags = 0; proximity_radius = 0; } virtual ~entity() { } virtual void destroy() { delete this; } virtual void tick() {} virtual void snap(int snapping_client) {} virtual bool take_damage(vec2 force, int dmg, int from) { return true; } }; class powerup : public entity { public: static const int phys_size = 14; enum { POWERUP_FLAG_HOOKABLE = 1 << 0, }; vec2 vel; class player* playerhooked; int type; int id; int subtype; // weapon type for instance? int numitems; // number off powerup items int flags; int spawntick; powerup(int _type, int _subtype = 0, int _numitems = 0, int _flags = 0); static void spawnrandom(int _lifespan); void tick(); void snap(int snapping_client); }; // game world. handles all entities class game_world { public: entity *first_entity; game_world() { first_entity = 0x0; } int find_entities(vec2 pos, float radius, entity **ents, int max) { int num = 0; for(entity *ent = first_entity; ent; ent = ent->next_entity) { if(distance(ent->pos, pos) < radius+ent->proximity_radius) { ents[num] = ent; num++; if(num == max) break; } } return num; } int find_entities(vec2 pos, float radius, entity **ents, int max, const int* types, int maxtypes) { int num = 0; for(entity *ent = first_entity; ent; ent = ent->next_entity) { for (int i = 0; i < maxtypes; i++) { if (ent->objtype != types[i]) continue; if(distance(ent->pos, pos) < radius+ent->proximity_radius) { ents[num] = ent; num++; if(num == max) break; } } } return num; } void insert_entity(entity *ent) { // insert it if(first_entity) first_entity->prev_entity = ent; ent->next_entity = first_entity; ent->prev_entity = 0x0; first_entity = ent; } void destroy_entity(entity *ent) { ent->flags |= entity::FLAG_DESTROY; // call destroy //remove_entity(ent); //ent->destroy(); } void remove_entity(entity *ent) { // remove if(ent->prev_entity) ent->prev_entity->next_entity = ent->next_entity; else first_entity = ent->next_entity; if(ent->next_entity) ent->next_entity->prev_entity = ent->prev_entity; } // void snap(int snapping_client) { for(entity *ent = first_entity; ent; ent = ent->next_entity) ent->snap(snapping_client); } void tick() { // update all objects for(entity *ent = first_entity; ent; ent = ent->next_entity) ent->tick(); // destroy objects marked for destruction entity *ent = first_entity; while(ent) { entity *next = ent->next_entity; if(ent->flags&entity::FLAG_DESTROY) { remove_entity(ent); ent->destroy(); } ent = next; } } }; static game_world world; // projectile entity class projectile : public entity { public: enum { PROJECTILE_FLAGS_EXPLODE = 1 << 0, }; vec2 vel; entity* powner; int lifespan; int id; int owner; int type; int flags; int damage; int sound_impact; float force; projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner, int damage, int flags = 0, float force = 0.0f, int sound_impact = -1) : entity(OBJTYPE_PROJECTILE) { static int current_id = 0; this->id = current_id++; this->type = type; this->pos = pos; this->vel = vel; this->lifespan = span; this->owner = owner; this->powner = powner; this->flags = flags; this->force = force; this->damage = damage; this->sound_impact = sound_impact; world.insert_entity(this); } void tick() { vec2 oldpos = pos; vel.y += 0.25f; pos += vel; lifespan--; // check player intersection as well entity* targetplayer = (entity*)intersect_player(oldpos, pos, oldpos, powner); if(targetplayer || lifespan < 0 || col_check_point((int)pos.x, (int)pos.y)) { if (lifespan >= 0) create_sound(pos, sound_impact); if (flags & PROJECTILE_FLAGS_EXPLODE) { create_explosion(oldpos, owner); } else if (targetplayer) { targetplayer->take_damage(normalize(vel) * force, damage, owner); } world.destroy_entity(this); } } void snap(int snapping_client) { obj_projectile *proj = (obj_projectile *)snap_new_item(OBJTYPE_PROJECTILE, id, sizeof(obj_projectile)); proj->x = (int)pos.x; proj->y = (int)pos.y; proj->vx = (int)vel.x; proj->vy = (int)vel.y; proj->type = type; } }; // player entity class player : public entity { public: static const int phys_size = 28; enum { WEAPON_NEEDRELOAD = 1 << 0, WEAPON_DROPONUNEQUIP = 1 << 1, WEAPON_DRAWSAMMO = 1 << 2, WEAPON_HASSECONDARY = 1 << 3, WEAPON_ISACTIVE = 1 << 4, // has the item WEAPON_AUTOFIRE = 1 << 5, WEAPON_PROJECTILETYPE_GUN = 0, WEAPON_PROJECTILETYPE_ROCKET = 1, WEAPON_PROJECTILETYPE_SHOTGUN = 2, // Gun // modifiers MODIFIER_HASACTIVATIONS = 1 << 0, MODIFIER_TIMELIMITED = 1 << 1, MODIFIER_ISACTIVE = 1 << 2, MODIFIER_NEEDSACTIVATION = 1 << 3, MODIFIER_RETURNFLAGS_OVERRIDEWEAPON = 1 << 0, MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY = 1 << 1, MODIFIER_RETURNFLAGS_OVERRIDEPOSITION = 1 << 2, MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY = 1 << 3, }; class weapon { public: entity* hitobjects[10]; int numobjectshit; // for melee, so we don't hit the same object more than once per bash int weapontype; int equiptime; int unequiptime; int numammo; int magsize; int nummagazines; int flags; int firetime; int reloadtime; int projectileclass; int damage; int sound_fire; int sound_equip; int sound_impact; int sound_impact_projectile; int visualtimeattack; float projectilevel; float projectilespan; float reach; // for melee float force; float recoilforce; float projoffsety; float projoffsetx; weapon() { weapontype = 0; numammo = 0; flags = 0; reloadtime = 0; projectileclass = 0; numobjectshit = 0; reach = 0.0f; force = 5.0f; damage = 1; sound_fire = -1; sound_equip = -1; sound_impact = -1; sound_impact_projectile = -1, visualtimeattack = 3; recoilforce = 0.0f; projoffsety = 0.0f; projoffsetx = 0.0f; } void setgun(int ammo = 10) { weapontype = WEAPON_TYPE_GUN; flags = 0;//WEAPON_DRAWSAMMO; numammo = ammo; projectileclass = WEAPON_PROJECTILETYPE_GUN; firetime = SERVER_TICK_SPEED/10; magsize = 0; projectilevel = 30.0f; projectilespan = 50.0f * 1.0f; sound_fire = SOUND_FIRE_GUN; sound_equip = SOUND_EQUIP_GUN; sound_impact_projectile = SOUND_IMPACT_PROJECTILE_GUN; projoffsety = -10.0f; projoffsetx = 24.0f; } void setrocket(int ammo = 10) { weapontype = WEAPON_TYPE_ROCKET; flags = WEAPON_DRAWSAMMO; numammo = ammo; projectileclass = WEAPON_PROJECTILETYPE_ROCKET; projectilevel = 15.0f; projectilespan = 50.0f * 5.0f; firetime = SERVER_TICK_SPEED * 4/5; magsize = 0; recoilforce = 5.0f; sound_fire = SOUND_FIRE_ROCKET; sound_equip = SOUND_EQUIP_ROCKET; sound_impact_projectile = SOUND_IMPACT_PROJECTILE_ROCKET; projoffsety = -17.0f; projoffsetx = 24.0f; } /*void setsniper(int ammo = 10) { weapontype = WEAPON_TYPE_SNIPER; flags = WEAPON_DRAWSAMMO | WEAPON_HASSECONDARY | WEAPON_NEEDRELOAD; numammo = ammo; projectileclass = WEAPON_PROJECTILETYPE_SNIPER; projectilevel = 30.0f; projectilespan = 50.0f * 5.0f; firetime = SERVER_TICK_SPEED; reloadtime = SERVER_TICK_SPEED/2; magsize = 2; recoilforce = 20.0f; }*/ void setshotgun(int ammo = 10) { weapontype = WEAPON_TYPE_SHOTGUN; flags = WEAPON_DRAWSAMMO | WEAPON_NEEDRELOAD; numammo = ammo; projectileclass = WEAPON_PROJECTILETYPE_SHOTGUN; projectilevel = 30.0f; projectilespan = 50.0f * 5.0f; firetime = SERVER_TICK_SPEED/2; reloadtime = SERVER_TICK_SPEED/2; magsize = 2; damage = 3; recoilforce = 5.0f; sound_fire = SOUND_FIRE_SHOTGUN; sound_equip = SOUND_EQUIP_SHOTGUN; sound_impact_projectile = SOUND_IMPACT_PROJECTILE_SHOTGUN; projoffsety = -17.0f; projoffsetx = 24.0f; } void setmelee(int ammo = 10) { weapontype = WEAPON_TYPE_MELEE; flags = 0;//WEAPON_AUTOFIRE; numammo = ammo; projectileclass = -1; firetime = SERVER_TICK_SPEED/5; reloadtime = 0; magsize = 2; numobjectshit = 0; reach = 15.0f; damage = 1; sound_fire = SOUND_FIRE_MELEE; sound_equip = SOUND_EQUIP_MELEE; sound_impact = SOUND_PLAYER_IMPACT; } void settype() { switch(weapontype) { case WEAPON_TYPE_GUN: { setgun(); break; } case WEAPON_TYPE_ROCKET: { setrocket(); break; } /*case WEAPON_TYPE_SNIPER: { setsniper(); break; }*/ case WEAPON_TYPE_SHOTGUN: { setshotgun(); break; } case WEAPON_TYPE_MELEE: { setmelee(); break; } default: break; } } int activate(player* player) { // create sound event for fire int projectileflags = 0; create_sound(player->pos, sound_fire); switch (weapontype) { case WEAPON_TYPE_ROCKET: projectileflags |= projectile::PROJECTILE_FLAGS_EXPLODE; case WEAPON_TYPE_GUN: //case WEAPON_TYPE_SNIPER: case WEAPON_TYPE_SHOTGUN: { if (flags & WEAPON_DRAWSAMMO) numammo--; // Create projectile new projectile(projectileclass, player->client_id, player->pos+vec2(0,projoffsety)+player->direction*projoffsetx, player->direction*projectilevel, projectilespan, player, damage, projectileflags, force, sound_impact_projectile); // recoil force if any if (recoilforce > 0.0f) { vec2 dir(player->direction.x,0.5); if (dir.x == 0.0f) dir.x = 0.5f; else dir = normalize(dir); player->vel -= dir * recoilforce; } return firetime; } case WEAPON_TYPE_MELEE: { // Start bash sequence numobjectshit = 0; return firetime; } default: return 0; } } void update(player* owner, int fire_timeout) { switch(weapontype) { case WEAPON_TYPE_MELEE: { // No more melee if (fire_timeout <= 0) return; // only one that needs update (for now) // do selection for the weapon and bash anything in it // check if we hit anything along the way int type = OBJTYPE_PLAYER; entity *ents[64]; vec2 dir = owner->pos + owner->direction * reach; float radius = length(dir * 0.5f); vec2 center = owner->pos + dir * 0.5f; int num = world.find_entities(center, radius, ents, 64, &type, 1); for (int i = 0; i < num; i++) { // Check if entity is a player if (ents[i] == owner) continue; // make sure we haven't hit this object before bool balreadyhit = false; for (int j = 0; j < numobjectshit; j++) { if (hitobjects[j] == ents[i]) balreadyhit = true; } if (balreadyhit) continue; // check so we are sufficiently close if (distance(ents[i]->pos, owner->pos) > (owner->phys_size * 2.0f)) continue; // hit a player, give him damage and stuffs... // create sound for bash create_sound(ents[i]->pos, sound_impact); // set his velocity to fast upward (for now) create_smoke(ents[i]->pos); hitobjects[numobjectshit++] = ents[i]; ents[i]->take_damage(vec2(0,10.0f), damage, owner->client_id); player* target = (player*)ents[i]; vec2 dir; if (length(target->pos - owner->pos) > 0.0f) dir = normalize(target->pos - owner->pos); else dir = vec2(0,-1); target->vel += dir * 10.0f + vec2(0,-10.0f); } break; } default: break; } } }; class modifier { public: vec2 activationdir; entity* hitobjects[10]; int numobjectshit; float velocity; int modifiertype; int duration; int numactivations; int activationtime; int cooldown; int movetime; int visualtimeattack; int currentactivation; int currentmovetime; int currentcooldown; int damage; int flags; int sound_impact; int sound_activate; modifier() { modifiertype = 0; duration = 0; numobjectshit = 0; numactivations = 0; activationtime = 0; cooldown = 0; movetime = 0; currentactivation = 0; currentmovetime = 0; currentcooldown =0; damage = 0; flags = 0; activationdir = vec2(0.0f, 1.0f); velocity = 0.0f; visualtimeattack = 0; sound_impact = -1; } void setninja() { modifiertype = MODIFIER_TYPE_NINJA; duration = SERVER_TICK_SPEED * 15; numactivations = -1; movetime = SERVER_TICK_SPEED / 5; activationtime = SERVER_TICK_SPEED / 2; cooldown = SERVER_TICK_SPEED; currentactivation = 0; currentmovetime = 0; numobjectshit = 0; damage = 3; flags = MODIFIER_TIMELIMITED | MODIFIER_NEEDSACTIVATION; velocity = 50.0f; visualtimeattack = 3; sound_impact = SOUND_PLAYER_IMPACT_NINJA; sound_activate = SOUND_FIRE_NINJA; } void settimefield() { modifiertype = MODIFIER_TYPE_TIMEFIELD; duration = SERVER_TICK_SPEED * 10; numactivations = -1; activationtime = SERVER_TICK_SPEED; numobjectshit = 0; currentactivation = 0; flags = MODIFIER_TIMELIMITED; velocity = 0.0f; } void settype() { switch (modifiertype) { case MODIFIER_TYPE_NINJA: { setninja(); break; } case MODIFIER_TYPE_TIMEFIELD: { settimefield(); break; } default: break; } } int updateninja(player* player) { if (currentactivation <= 0) return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; currentactivation--; currentmovetime--; if (currentmovetime == 0) { // reset player velocity player->vel *= 0.2f; //return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; } if (currentmovetime > 0) { // Set player velocity player->vel = activationdir * velocity; vec2 oldpos = player->pos; move_box(&player->pos, &player->vel, vec2(player->phys_size, player->phys_size), 0.0f); // reset velocity so the client doesn't predict stuff player->vel = vec2(0.0f,0.0f); if ((currentmovetime % 2) == 0) { create_smoke(player->pos); } // check if we hit anything along the way { int type = OBJTYPE_PLAYER; entity *ents[64]; vec2 dir = player->pos - oldpos; float radius = length(dir * 0.5f); vec2 center = oldpos + dir * 0.5f; int num = world.find_entities(center, radius, ents, 64, &type, 1); for (int i = 0; i < num; i++) { // Check if entity is a player if (ents[i] == player) continue; // make sure we haven't hit this object before bool balreadyhit = false; for (int j = 0; j < numobjectshit; j++) { if (hitobjects[j] == ents[i]) balreadyhit = true; } if (balreadyhit) continue; // check so we are sufficiently close if (distance(ents[i]->pos, player->pos) > (player->phys_size * 2.0f)) continue; // hit a player, give him damage and stuffs... create_sound(ents[i]->pos, sound_impact); // set his velocity to fast upward (for now) hitobjects[numobjectshit++] = ents[i]; ents[i]->take_damage(vec2(0,10.0f), damage, player->client_id); } } return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON | MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION|MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY; } // move char, and check intersection from us to target return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON | MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY; } int activateninja(player* player) { // ok then, activate ninja activationdir = player->direction; currentactivation = activationtime; currentmovetime = movetime; currentcooldown = cooldown; // reset hit objects numobjectshit = 0; create_sound(player->pos, SOUND_FIRE_NINJA); return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; } int activate(player* player) { if (flags & MODIFIER_NEEDSACTIVATION) { if (!numactivations) return 0; numactivations--; } switch (modifiertype) { case MODIFIER_TYPE_NINJA: { return activateninja(player); } /*case MODIFIER_TYPE_TIMEFIELD: { updatetimefield(); break; }*/ default: return 0; } } int update(player* player) { switch (modifiertype) { case MODIFIER_TYPE_NINJA: { return updateninja(player); } /*case MODIFIER_TYPE_TIMEFIELD: { updatetimefield(); break; }*/ default: return 0; } } }; enum { PLAYER_FLAGS_ISRELOADING = 1 << 0, PLAYER_FLAGS_ISEQUIPPING = 1 << 1, }; weapon lweapons[WEAPON_NUMWEAPONS]; modifier modifiers[MODIFIER_NUMMODIFIERS]; int iactiveweapon; int inextweapon; int equip_time; int client_id; int flags; char name[32]; player_input previnput; player_input input; int tick_count; int damage_taken_tick; vec2 vel; vec2 direction; int jumped; int airjumped; //int firing; int hooking; int fire_timeout; int reload_timeout; int health; int armor; int score; // sounds int sound_player_jump; int sound_player_land; int sound_player_hurt_short; int sound_player_hurt_long; int sound_player_spawn; int sound_player_chain_loop; int sound_player_chain_impact; int sound_player_impact; int sound_player_impact_ninja; int sound_player_die; int sound_player_switchweapon; player* phookedplayer; powerup* phookedpowerup; int numhooked; vec2 hook_pos; vec2 hook_dir; player() : entity(OBJTYPE_PLAYER) { reset(); //firing = 0; // setup weaponflags and stuff lweapons[WEAPON_TYPE_GUN].setgun(); lweapons[WEAPON_TYPE_ROCKET].setrocket(); //lweapons[WEAPON_TYPE_SNIPER].setsniper(); lweapons[WEAPON_TYPE_SHOTGUN].setshotgun(); lweapons[WEAPON_TYPE_MELEE].setmelee(); modifiers[MODIFIER_TYPE_NINJA].setninja(); modifiers[MODIFIER_TYPE_TIMEFIELD].settimefield(); //modifiers[MODIFIER_TYPE_NINJA].flags |= MODIFIER_ISACTIVE; sound_player_jump = SOUND_PLAYER_JUMP; sound_player_hurt_short = SOUND_PLAYER_HURT_SHORT; sound_player_hurt_long = SOUND_PLAYER_HURT_LONG; sound_player_spawn = SOUND_PLAYER_SPAWN; sound_player_chain_loop = SOUND_PLAYER_CHAIN_LOOP; sound_player_chain_impact = SOUND_PLAYER_CHAIN_IMPACT; sound_player_impact = SOUND_PLAYER_IMPACT; sound_player_impact_ninja = SOUND_PLAYER_IMPACT_NINJA; sound_player_die = SOUND_PLAYER_DIE; sound_player_switchweapon = SOUND_PLAYER_SWITCHWEAPON; sound_player_land = SOUND_PLAYER_LAND; } void reset() { equip_time = 0; phookedplayer = 0; numhooked = 0; proximity_radius = phys_size; name[0] = 'n'; name[1] = 'o'; name[2] = 'o'; name[3] = 'b'; name[4] = 0; pos = vec2(100.0f, 0.0f); vel = vec2(0.0f, 0.0f); direction = vec2(0.0f, 1.0f); client_id = -1; tick_count = 0; score = 0; flags = 0; } virtual void destroy() { flags = 0; } void respawn() { health = PLAYER_MAXHEALTH; armor = 0; hooking = 0; phookedplayer = 0; phookedpowerup = 0; numhooked = 0; fire_timeout = 0; reload_timeout = 0; iactiveweapon = 0; inextweapon = -1; equip_time = 0; jumped = 0; airjumped = 0; mem_zero(&input, sizeof(input)); vel = vec2(0.0f, 0.0f); int start, num; map_get_type(1, &start, &num); if(num) { mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + (rand()%num), NULL, NULL); pos = vec2(sp->x, sp->y); } else pos = vec2(100.0f, -60.0f); // reset active flags for (int i = 0; i < WEAPON_NUMWEAPONS; i++) { // reset and remove lweapons[i].settype(); lweapons[i].flags &= ~WEAPON_ISACTIVE; } // TEMP REMOVE /*for (int i = 0; i < WEAPON_NUMWEAPONS; i++) { lweapons[i].settype(); lweapons[i].flags |= WEAPON_ISACTIVE; }*/ lweapons[WEAPON_TYPE_MELEE].flags |= WEAPON_ISACTIVE; // Add gun as default weapon iactiveweapon = WEAPON_TYPE_GUN; lweapons[WEAPON_TYPE_GUN].numammo = 10; lweapons[WEAPON_TYPE_GUN].flags |= WEAPON_ISACTIVE; create_sound(pos, sound_player_spawn); } bool is_grounded() { if(col_check_point((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2+5))) return true; if(col_check_point((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2+5))) return true; return false; } // Disable weapon activation if this returns true int handlemodifiers() { int returnflags = 0; for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++) { if (modifiers[i].flags & MODIFIER_ISACTIVE) { modifiers[i].duration--; modifiers[i].currentcooldown--; // Check if it should activate if (modifiers[i].currentcooldown <= 0 && (modifiers[i].flags & MODIFIER_NEEDSACTIVATION) && input.fire && !(previnput.fire)) { returnflags |= modifiers[i].activate(this); } returnflags |= modifiers[i].update(this); // remove active if timed out if (modifiers[i].duration <= 0 && modifiers[i].currentactivation <= 0) modifiers[i].flags &= ~MODIFIER_ISACTIVE; } } return returnflags; } void handleweapon() { // handle weapon if(input.fire && (!previnput.fire || lweapons[iactiveweapon].flags & WEAPON_AUTOFIRE) && !(flags & PLAYER_FLAGS_ISEQUIPPING) && !reload_timeout) { if(fire_timeout == 0) { if (lweapons[iactiveweapon].numammo || !(lweapons[iactiveweapon].flags & WEAPON_DRAWSAMMO)) { // Decrease ammo fire_timeout = lweapons[iactiveweapon].activate(this); } else if ((lweapons[iactiveweapon].flags & WEAPON_NEEDRELOAD) && lweapons[iactiveweapon].nummagazines) { // reload reload_timeout = lweapons[iactiveweapon].reloadtime; lweapons[iactiveweapon].nummagazines--; lweapons[iactiveweapon].numammo = lweapons[iactiveweapon].magsize; } } } // update active weapon lweapons[iactiveweapon].update(this, fire_timeout); } void handlehook() { // handle hook if(input.hook) { if(hooking == 0) { hooking = 1; hook_pos = pos; hook_dir = direction; // Sound create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STARTLOOP); } else if(hooking == 1) { vec2 new_pos = hook_pos+hook_dir*hook_fire_speed; // Check against other players and powerups first player* targetplayer = 0; powerup* targetpowerup = 0; { static const int typelist[2] = { OBJTYPE_PLAYER, OBJTYPE_POWERUP}; entity *ents[64]; vec2 dir = new_pos - hook_pos; float radius = length(dir * 0.5f); vec2 center = hook_pos + dir * 0.5f; int num = world.find_entities(center, radius, ents, 64,typelist,2); for (int i = 0; i < num; i++) { // Check if entity is a player if (ents[i] == this || (targetplayer && targetpowerup)) continue; if (!targetplayer && ents[i]->objtype == OBJTYPE_PLAYER) { // temp, set hook pos to our position if (((player*)ents[i])->phookedplayer != this) targetplayer = (player*)ents[i]; } else if (!targetpowerup && ents[i]->objtype == OBJTYPE_POWERUP && (((powerup*)ents[i])->flags & powerup::POWERUP_FLAG_HOOKABLE)) { targetpowerup = (powerup*)ents[i]; } } } //player* targetplayer = intersect_player(hook_pos, hook_pos, new_pos, this); if (targetplayer) { // So he can't move "normally" new_pos = targetplayer->pos; phookedplayer = targetplayer; targetplayer->numhooked++; hooking = 3; create_sound(pos, sound_player_chain_impact); // stop looping chain sound create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); } else if (targetpowerup) { new_pos = targetpowerup->pos; phookedpowerup = targetpowerup; phookedpowerup->playerhooked = this; hooking = 4; create_sound(pos, sound_player_chain_impact); // stop looping chain sound create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); } else if(intersect_line(hook_pos, new_pos, &new_pos)) { hooking = 2; create_sound(pos, sound_player_chain_impact); // stop looping chain sound create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); } else if(distance(pos, new_pos) > hook_length) { hooking = -1; create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); } hook_pos = new_pos; } else if(hooking == 2) { vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel; // the hook as more power to drag you up then down. // this makes it easier to get on top of an platform if(hookvel.y > 0) hookvel.y *= 0.3f; // the hook will boost it's power if the player wants to move // in that direction. otherwise it will dampen everything abit if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) hookvel.x *= 0.95f; else hookvel.x *= 0.75f; vec2 new_vel = vel+hookvel; // check if we are under the legal limit for the hook if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) vel = new_vel; // no problem. apply } else if (hooking == 3) { // hmm, force the targetplayer towards us if possible, otherwise us towards them if they are already hooked if (phookedplayer) { if (phookedplayer->hooking > 1) { // Drag us towards target player vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel; if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) hookvel.x *= 0.95f; else hookvel.x *= 0.75f; // Apply the velocity // the hook will boost it's power if the player wants to move // in that direction. otherwise it will dampen everything abit vec2 new_vel = vel+hookvel; // check if we are under the legal limit for the hook if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) vel = new_vel; // no problem. apply } else { // Drag targetplayer towards us vec2 hookvel = normalize(pos-hook_pos)*hook_drag_accel; // Apply the velocity // the hook will boost it's power if the player wants to move // in that direction. otherwise it will dampen everything abit vec2 new_vel = phookedplayer->vel+hookvel; // check if we are under the legal limit for the hook if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) phookedplayer->vel = new_vel; // no problem. apply } hook_pos = phookedplayer->pos; // if hooked player dies, release the hook } else { hooking = -1; phookedplayer = 0; } } else if (hooking == 4) { // Have a powerup, drag it towards us vec2 hookvel = normalize(pos-hook_pos)*hook_drag_accel; // Apply the velocity // the hook will boost it's power if the player wants to move // in that direction. otherwise it will dampen everything abit vec2 new_vel = phookedpowerup->vel+hookvel; // check if we are under the legal limit for the hook if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) phookedpowerup->vel = new_vel; // no problem. apply hook_pos = phookedpowerup->pos; } } else { hooking = 0; hook_pos = pos; if (phookedplayer) { phookedplayer->numhooked--; phookedplayer = 0; } } } void getattackticks(int& curattack, int& attacklen, int& visualtimeattack) { // time left from current attack (if any) // first check modifiers (ninja...) for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++) { if ((modifiers[i].flags & (MODIFIER_ISACTIVE | MODIFIER_NEEDSACTIVATION)) == (MODIFIER_ISACTIVE | MODIFIER_NEEDSACTIVATION)) { curattack = modifiers[i].currentactivation; attacklen = modifiers[i].activationtime; visualtimeattack = modifiers[i].visualtimeattack; return; } } // otherwise current fire timeout curattack = fire_timeout; attacklen = lweapons[iactiveweapon].firetime; visualtimeattack = lweapons[iactiveweapon].visualtimeattack; } virtual void tick() { tick_count++; // fetch some info bool grounded = is_grounded(); direction = get_direction(input.angle); // decrease reload timer if(fire_timeout) fire_timeout--; if (reload_timeout) reload_timeout--; // Switch weapons if (flags & PLAYER_FLAGS_ISEQUIPPING) { equip_time--; if (equip_time <= 0) { if (inextweapon >= 0) { equip_time = SERVER_TICK_SPEED * lweapons[inextweapon].equiptime; iactiveweapon = inextweapon; inextweapon = -1; // Send switch weapon event to client? } else { flags &= ~PLAYER_FLAGS_ISEQUIPPING; } } } else if (input.activeweapon && iactiveweapon != (input.activeweapon & ~0x80000000)) { input.activeweapon &= ~0x80000000; // check which weapon to activate if (input.activeweapon >= 0 && input.activeweapon < WEAPON_NUMWEAPONS && (lweapons[input.activeweapon].flags & WEAPON_ISACTIVE)) { inextweapon = input.activeweapon; equip_time = SERVER_TICK_SPEED * lweapons[iactiveweapon].unequiptime; // unequip current flags |= PLAYER_FLAGS_ISEQUIPPING; create_sound(pos, sound_player_switchweapon); } } // don't do any weapon activations if modifier is currently overriding int modifierflags = handlemodifiers(); if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEWEAPON)) handleweapon(); handlehook(); // handle movement if(grounded) { if (airjumped) create_sound(pos, SOUND_PLAYER_LAND); airjumped = 0; } float elast = 0.0f; if (!numhooked) { // I'm hooked by someone, so don't do any movement plz (temp) if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY)) { if(grounded) { // ground movement if(input.left) { if(vel.x > -ground_control_speed) { vel.x -= ground_control_accel; if(vel.x < -ground_control_speed) vel.x = -ground_control_speed; } } else if(input.right) { if(vel.x < ground_control_speed) { vel.x += ground_control_accel; if(vel.x > ground_control_speed) vel.x = ground_control_speed; } } else vel.x *= ground_friction; // ground fiction } else { // air movement if(input.left) { if(vel.x > -air_control_speed) vel.x -= air_control_accel; } else if(input.right) { if(vel.x < air_control_speed) vel.x += air_control_accel; } else vel.x *= air_friction; // air fiction } if(input.jump) { if(jumped == 0) { if(grounded) { create_sound(pos, sound_player_jump); vel.y = -ground_jump_speed; jumped++; } /* else if(airjumped == 0) { vel.y = -12; airjumped++; jumped++; }*/ } else if (!grounded) { airjumped++; } } else jumped = 0; } // meh, go through all players and stop their hook on me /* for(entity *ent = world.first_entity; ent; ent = ent->next_entity) { if (ent && ent->objtype == OBJTYPE_PLAYER) { player *p = (player*)ent; if(p != this) { float d = distance(pos, p->pos); vec2 dir = normalize(pos - p->pos); if(d < phys_size*1.5f) { float a = phys_size*1.5f - d; vel = vel + dir*a; } if(p->phookedplayer == this) { if(d > phys_size*2.5f) { elast = 0.0f; vel = vel - dir*2.5f; } } } } }*/ // gravity if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY)) vel.y += gravity; } if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEPOSITION)) move_box(&pos, &vel, vec2(phys_size, phys_size), elast); } void die() { create_sound(pos, sound_player_die); // release our hooked player if (phookedplayer) { phookedplayer->numhooked--; phookedplayer = 0; hooking = -1; numhooked = 0; } respawn(); // meh, go through all players and stop their hook on me for(entity *ent = world.first_entity; ent; ent = ent->next_entity) { if (ent && ent->objtype == OBJTYPE_PLAYER) { player* p = (player*)ent; if (p->phookedplayer == this) { p->phookedplayer = 0; p->hooking = -1; //p->numhooked--; } } } } virtual bool take_damage(vec2 force, int dmg, int from) { vel += force; if(armor) { armor -= 1; dmg--; } if(dmg > armor) { dmg -= armor; armor = 0; health -= dmg; } else armor -= dmg; /* int armordmg = (dmg+1)/2; int healthdmg = dmg-armordmg; if(armor < armordmg) { healthdmg += armordmg - armor; armor = 0; } else armor -= armordmg; health -= healthdmg; */ // create healthmod indicator create_healthmod(pos, dmg); damage_taken_tick = tick_count+50; // check for death if(health <= 0) { // apply score if(from != -1) { if(from == client_id) score--; else { player *p = get_player(from); p->score++; } } die(); return false; } if (dmg > 2) create_sound(pos, sound_player_hurt_long); else create_sound(pos, sound_player_hurt_short); // spawn blood? return true; } virtual void snap(int snaping_client) { obj_player *player = (obj_player *)snap_new_item(OBJTYPE_PLAYER, client_id, sizeof(obj_player)); client_info info; if(server_getclientinfo(client_id, &info)) snap_encode_string(info.name, player->name, strlen(info.name), 32); player->x = (int)pos.x; player->y = (int)pos.y; player->vx = (int)vel.x; player->vy = (int)vel.y; player->emote = EMOTE_NORMAL; player->ammocount = lweapons[iactiveweapon].numammo; player->health = 0; player->armor = 0; player->local = 0; player->clientid = client_id; player->weapon = iactiveweapon; player->modifier = 0; for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++) { // add active modifiers if (modifiers[i].flags & MODIFIER_ISACTIVE) player->modifier |= 1 << i; } // get current attack ticks getattackticks(player->attackticks, player->attacklen, player->visualtimeattack); if(client_id == snaping_client) { player->local = 1; player->health = health; player->armor = armor; } if(length(vel) > 15.0f) player->emote = EMOTE_HAPPY; if(damage_taken_tick > tick_count) player->emote = EMOTE_PAIN; if(player->emote == EMOTE_NORMAL) { if((tick_count%(50*5)) < 10) player->emote = EMOTE_BLINK; } player->hook_active = hooking>0?1:0; player->hook_x = (int)hook_pos.x; player->hook_y = (int)hook_pos.y; player->angle = input.angle; player->score = score; } }; // POWERUP /////////////////////// powerup::powerup(int _type, int _subtype, int _numitems, int _flags) : entity(OBJTYPE_POWERUP) { static int current_id = 0; playerhooked = 0; id = current_id++; vel = vec2(0.0f,0.0f); type = _type; subtype = _subtype; numitems = _numitems; flags = _flags; // set radius (so it can collide and be hooked and stuff) proximity_radius = phys_size; spawntick = -1; world.insert_entity(this); } void powerup::spawnrandom(int _lifespan) { return; /* vec2 pos; int start, num; map_get_type(1, &start, &num); if(!num) return; mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + (rand()%num), NULL, NULL); pos = vec2(sp->x, sp->y); // Check if there already is a powerup at that location { int type = OBJTYPE_POWERUP; entity *ents[64]; int num = world.find_entities(pos, 5.0f, ents, 64,&type,1); for (int i = 0; i < num; i++) { if (ents[i]->objtype == OBJTYPE_POWERUP) { // location busy return; } } } powerup* ppower = new powerup(rand() % POWERUP_TYPE_NUMPOWERUPS,_lifespan); ppower->pos = pos; if (ppower->type == POWERUP_TYPE_WEAPON) { ppower->subtype = rand() % WEAPON_NUMWEAPONS; ppower->numitems = 10; } else if (ppower->type == POWERUP_TYPE_HEALTH || ppower->type == POWERUP_TYPE_ARMOR) { ppower->numitems = rand() % 5; } ppower->flags |= POWERUP_FLAG_HOOKABLE;*/ } void powerup::tick() { // wait for respawn if(spawntick > 0) { if(server_tick() > spawntick) spawntick = -1; else return; } vec2 oldpos = pos; //vel.y += 0.25f; pos += vel; move_box(&pos, &vel, vec2(phys_size, phys_size), 0.0f); // Check if a player intersected us vec2 meh; player* pplayer = intersect_player(pos, pos + vec2(0,16), meh, 0); if (pplayer) { // player picked us up, is someone was hooking us, let them go if (playerhooked) playerhooked->hooking = -1; int respawntime = -1; switch (type) { case POWERUP_TYPE_HEALTH: { if(pplayer->health < PLAYER_MAXHEALTH) { pplayer->health = min(PLAYER_MAXHEALTH, pplayer->health + numitems); respawntime = 20; } break; } case POWERUP_TYPE_ARMOR: { if(pplayer->armor < PLAYER_MAXARMOR) { pplayer->armor = min(PLAYER_MAXARMOR, pplayer->armor + numitems); respawntime = 20; } break; } case POWERUP_TYPE_WEAPON: { if (pplayer->lweapons[subtype].flags & player::WEAPON_ISACTIVE) { // add ammo /* if (pplayer->lweapons[subtype].flags & player::WEAPON_NEEDRELOAD) { int numtoadd = min(numitems, pplayer->lweapons[subtype].magsize); pplayer->lweapons[subtype].numammo = min(10, pplayer->lweapons[subtype].numammo + numtoadd); pplayer->lweapons[subtype].nummagazines += (numitems - numtoadd) % pplayer->lweapons[subtype].magsize; } else*/ if(pplayer->lweapons[subtype].numammo < 10) { respawntime = 20; pplayer->lweapons[subtype].numammo = min(10, pplayer->lweapons[subtype].numammo + numitems); } } else { pplayer->lweapons[subtype].settype(); pplayer->lweapons[subtype].flags |= player::WEAPON_ISACTIVE; respawntime = 20; } break; } case POWERUP_TYPE_NINJA: { respawntime = 60; // reset and activate pplayer->modifiers[MODIFIER_TYPE_NINJA].settype(); pplayer->modifiers[MODIFIER_TYPE_NINJA].flags |= player::MODIFIER_ISACTIVE; break; } //POWERUP_TYPE_TIMEFIELD = 4, default: break; }; if(respawntime >= 0) spawntick = server_tick() + server_tickspeed() * respawntime; //world.destroy_entity(this); } } void powerup::snap(int snapping_client) { if(spawntick != -1) return; obj_powerup *powerup = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup)); powerup->x = (int)pos.x; powerup->y = (int)pos.y; powerup->vx = (int)vel.x; powerup->vy = (int)vel.y; powerup->type = type; powerup->subtype = subtype; } // POWERUP END /////////////////////// static player players[MAX_CLIENTS]; player *get_player(int index) { return &players[index]; } void create_healthmod(vec2 p, int amount) { ev_healthmod *ev = (ev_healthmod *)events.create(EVENT_HEALTHMOD, sizeof(ev_healthmod)); ev->x = (int)p.x; ev->y = (int)p.y; ev->amount = amount; } void create_explosion(vec2 p, int owner, bool bnodamage) { // create the event ev_explosion *ev = (ev_explosion *)events.create(EVENT_EXPLOSION, sizeof(ev_explosion)); ev->x = (int)p.x; ev->y = (int)p.y; if (!bnodamage) { // deal damage entity *ents[64]; const float radius = 128.0f; int num = world.find_entities(p, radius, ents, 64); for(int i = 0; i < num; i++) { vec2 diff = ents[i]->pos - p; vec2 forcedir(0,1); if (length(diff)) forcedir = normalize(diff); float l = length(diff); float dmg = 5 * (1 - (l/radius)); if((int)dmg) { ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner);/* && ents[i]->objtype == OBJTYPE_PLAYER && owner >= 0) { player *p = (player*)ents[i]; if(p->client_id == owner) p->score--; else ((player*)ents[owner])->score++; }*/ } } } } void create_smoke(vec2 p) { // create the event ev_explosion *ev = (ev_explosion *)events.create(EVENT_SMOKE, sizeof(ev_explosion)); ev->x = (int)p.x; ev->y = (int)p.y; } void create_sound(vec2 pos, int sound, int loopingflags) { if (sound < 0) return; // create a sound ev_sound *ev = (ev_sound *)events.create(EVENT_SOUND, sizeof(ev_sound)); ev->x = (int)pos.x; ev->y = (int)pos.y; ev->sound = sound | loopingflags; } player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis) { // Find other players entity *ents[64]; vec2 dir = pos1 - pos0; float radius = length(dir * 0.5f); vec2 center = pos0 + dir * 0.5f; int num = world.find_entities(center, radius, ents, 64); for (int i = 0; i < num; i++) { // Check if entity is a player if (ents[i] != notthis && ents[i]->objtype == OBJTYPE_PLAYER) { // temp, set hook pos to our position new_pos = ents[i]->pos; return (player*)ents[i]; } } return 0; } // Server hooks static int addtick = SERVER_TICK_SPEED * 5; void mods_tick() { // clear all events events.clear(); world.tick(); if (addtick <= 0) { powerup::spawnrandom(SERVER_TICK_SPEED * 5); addtick = SERVER_TICK_SPEED * 5; } addtick--; } void mods_snap(int client_id) { world.snap(client_id); events.snap(client_id); } void mods_client_input(int client_id, void *input) { players[client_id].previnput = players[client_id].input; players[client_id].input = *(player_input*)input; } void mods_client_enter(int client_id) { players[client_id].reset(); players[client_id].client_id = client_id; players[client_id].respawn(); world.insert_entity(&players[client_id]); } void mods_client_drop(int client_id) { players[client_id].client_id = -1; world.remove_entity(&players[client_id]); } void mods_init() { col_init(32); int start, num; map_get_type(MAPRES_ITEM, &start, &num); for(int i = 0; i < num; i++) { mapres_item *it = (mapres_item *)map_get_item(start+i, 0, 0); int type = -1; int subtype = -1; int numitems = 1; switch(it->type) { case ITEM_WEAPON_GUN: type = POWERUP_TYPE_WEAPON; subtype = WEAPON_TYPE_GUN; break; case ITEM_WEAPON_SHOTGUN: type = POWERUP_TYPE_WEAPON; subtype = WEAPON_TYPE_SHOTGUN; numitems = 5; break; case ITEM_WEAPON_ROCKET: type = POWERUP_TYPE_WEAPON; subtype = WEAPON_TYPE_ROCKET; numitems = 5; break; /*case ITEM_WEAPON_SNIPER: type = POWERUP_TYPE_WEAPON; subtype = WEAPON_TYPE_ROCKET; break;*/ case ITEM_WEAPON_HAMMER: type = POWERUP_TYPE_WEAPON; subtype = WEAPON_TYPE_MELEE; break; case ITEM_HEALTH_1: type = POWERUP_TYPE_HEALTH; numitems = 1; break; case ITEM_HEALTH_5: type = POWERUP_TYPE_HEALTH; numitems = 5; break; case ITEM_HEALTH_10: type = POWERUP_TYPE_HEALTH; numitems = 10; break; case ITEM_ARMOR_1: type = POWERUP_TYPE_ARMOR; numitems = 1; break; case ITEM_ARMOR_5: type = POWERUP_TYPE_ARMOR; numitems = 5; break; case ITEM_ARMOR_10: type = POWERUP_TYPE_ARMOR; numitems = 10; break; }; powerup* ppower = new powerup(type, subtype, numitems); ppower->pos.x = it->x; ppower->pos.y = it->y; } /* powerup* ppower = new powerup(rand() % POWERUP_TYPE_NUMPOWERUPS,_lifespan); ppower->pos = pos; if (ppower->type == POWERUP_TYPE_WEAPON) { ppower->subtype = rand() % WEAPON_NUMWEAPONS; ppower->numitems = 10; } else if (ppower->type == POWERUP_TYPE_HEALTH || ppower->type == POWERUP_TYPE_ARMOR) { ppower->numitems = rand() % 5; } ppower->flags |= POWERUP_FLAG_HOOKABLE; */ //powerup::spawnrandom(SERVER_TICK_SPEED * 5); } void mods_shutdown() {} void mods_presnap() {} void mods_postsnap() {}