From a2566b3ebd93e0bbc55a920a7be08054a9377f11 Mon Sep 17 00:00:00 2001 From: Magnus Auvinen Date: Sat, 15 Dec 2007 10:24:49 +0000 Subject: cleaned up code structure a bit --- src/game/server/data/createdir.txt | 1 - src/game/server/game_server.cpp | 1860 ------------------------------------ src/game/server/game_server.h | 2 - src/game/server/gs_common.cpp | 264 +++++ src/game/server/gs_common.h | 327 +++++++ src/game/server/gs_game_ctf.cpp | 178 ++++ src/game/server/gs_game_ctf.h | 33 + src/game/server/gs_game_dm.cpp | 42 + src/game/server/gs_game_dm.h | 7 + src/game/server/gs_game_tdm.cpp | 32 + src/game/server/gs_game_tdm.h | 10 + src/game/server/gs_server.cpp | 1859 +++++++++++++++++++++++++++++++++++ src/game/server/srv_common.cpp | 264 ----- src/game/server/srv_common.h | 327 ------- src/game/server/srv_ctf.cpp | 178 ---- src/game/server/srv_ctf.h | 33 - src/game/server/srv_dm.cpp | 42 - src/game/server/srv_dm.h | 7 - src/game/server/srv_tdm.cpp | 32 - src/game/server/srv_tdm.h | 10 - 20 files changed, 2752 insertions(+), 2756 deletions(-) delete mode 100644 src/game/server/data/createdir.txt delete mode 100644 src/game/server/game_server.cpp delete mode 100644 src/game/server/game_server.h create mode 100644 src/game/server/gs_common.cpp create mode 100644 src/game/server/gs_common.h create mode 100644 src/game/server/gs_game_ctf.cpp create mode 100644 src/game/server/gs_game_ctf.h create mode 100644 src/game/server/gs_game_dm.cpp create mode 100644 src/game/server/gs_game_dm.h create mode 100644 src/game/server/gs_game_tdm.cpp create mode 100644 src/game/server/gs_game_tdm.h create mode 100644 src/game/server/gs_server.cpp delete mode 100644 src/game/server/srv_common.cpp delete mode 100644 src/game/server/srv_common.h delete mode 100644 src/game/server/srv_ctf.cpp delete mode 100644 src/game/server/srv_ctf.h delete mode 100644 src/game/server/srv_dm.cpp delete mode 100644 src/game/server/srv_dm.h delete mode 100644 src/game/server/srv_tdm.cpp delete mode 100644 src/game/server/srv_tdm.h (limited to 'src/game/server') diff --git a/src/game/server/data/createdir.txt b/src/game/server/data/createdir.txt deleted file mode 100644 index 258b42d5..00000000 --- a/src/game/server/data/createdir.txt +++ /dev/null @@ -1 +0,0 @@ -this file is here to make sure that this directory gets created diff --git a/src/game/server/game_server.cpp b/src/game/server/game_server.cpp deleted file mode 100644 index 658e72c9..00000000 --- a/src/game/server/game_server.cpp +++ /dev/null @@ -1,1860 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include -#include "../version.h" -#include "game_server.h" -#include "srv_common.h" -#include "srv_ctf.h" -#include "srv_tdm.h" -#include "srv_dm.h" - -data_container *data = 0x0; - -class player* get_player(int index); -void create_damageind(vec2 p, float angle_mod, int amount); -void create_explosion(vec2 p, int owner, int weapon, bool bnodamage); -void create_smoke(vec2 p); -void create_spawn(vec2 p); -void create_death(vec2 p); -void create_sound(vec2 pos, int sound, int mask=-1); -class player *intersect_player(vec2 pos0, vec2 pos1, vec2 &new_pos, class entity *notthis = 0); - -game_world *world; - -////////////////////////////////////////////////// -// Event handler -////////////////////////////////////////////////// -event_handler::event_handler() -{ - clear(); -} - -void *event_handler::create(int type, int size, int mask) -{ - if(num_events == MAX_EVENTS) - return 0; - if(current_offset+size >= MAX_DATASIZE) - return 0; - - void *p = &data[current_offset]; - offsets[num_events] = current_offset; - types[num_events] = type; - sizes[num_events] = size; - client_masks[num_events] = mask; - current_offset += size; - num_events++; - return p; -} - -void event_handler::clear() -{ - num_events = 0; - current_offset = 0; -} - -void event_handler::snap(int snapping_client) -{ - for(int i = 0; i < num_events; i++) - { - if(cmask_is_set(client_masks[i], snapping_client)) - { - ev_common *ev = (ev_common *)&data[offsets[i]]; - if(distance(players[snapping_client].pos, vec2(ev->x, ev->y)) < 1500.0f) - { - void *d = snap_new_item(types[i], i, sizes[i]); - mem_copy(d, &data[offsets[i]], sizes[i]); - } - } - } -} - -event_handler events; - -////////////////////////////////////////////////// -// Entity -////////////////////////////////////////////////// -entity::entity(int objtype) -{ - this->objtype = objtype; - pos = vec2(0,0); - flags = FLAG_PHYSICS; - proximity_radius = 0; - - id = snap_new_id(); - - next_entity = 0; - prev_entity = 0; - prev_type_entity = 0; - next_type_entity = 0; -} - -entity::~entity() -{ - snap_free_id(id); -} - -////////////////////////////////////////////////// -// game world -////////////////////////////////////////////////// -game_world::game_world() -{ - paused = false; - reset_requested = false; - first_entity = 0x0; - for(int i = 0; i < NUM_ENT_TYPES; i++) - first_entity_types[i] = 0; -} - -int game_world::find_entities(vec2 pos, float radius, entity **ents, int max) -{ - int num = 0; - for(entity *ent = first_entity; ent; ent = ent->next_entity) - { - if(!(ent->flags&entity::FLAG_PHYSICS)) - continue; - - if(distance(ent->pos, pos) < radius+ent->proximity_radius) - { - ents[num] = ent; - num++; - if(num == max) - break; - } - } - - return num; -} - -int game_world::find_entities(vec2 pos, float radius, entity **ents, int max, const int* types, int maxtypes) -{ - int num = 0; - for(int t = 0; t < maxtypes; t++) - { - for(entity *ent = first_entity_types[types[t]]; ent; ent = ent->next_type_entity) - { - if(!(ent->flags&entity::FLAG_PHYSICS)) - continue; - - if(distance(ent->pos, pos) < radius+ent->proximity_radius) - { - ents[num] = ent; - num++; - if(num == max) - break; - } - } - } - - return num; -} - -void game_world::insert_entity(entity *ent) -{ - entity *cur = first_entity; - while(cur) - { - dbg_assert(cur != ent, "err"); - cur = cur->next_entity; - } - - // insert it - if(first_entity) - first_entity->prev_entity = ent; - ent->next_entity = first_entity; - ent->prev_entity = 0x0; - first_entity = ent; - - // into typelist aswell - if(first_entity_types[ent->objtype]) - first_entity_types[ent->objtype]->prev_type_entity = ent; - ent->next_type_entity = first_entity_types[ent->objtype]; - ent->prev_type_entity = 0x0; - first_entity_types[ent->objtype] = ent; -} - -void game_world::destroy_entity(entity *ent) -{ - ent->set_flag(entity::FLAG_DESTROY); -} - -void game_world::remove_entity(entity *ent) -{ - // not in the list - if(!ent->next_entity && !ent->prev_entity && first_entity != ent) - return; - - // 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; - - if(ent->prev_type_entity) - ent->prev_type_entity->next_type_entity = ent->next_type_entity; - else - first_entity_types[ent->objtype] = ent->next_type_entity; - if(ent->next_type_entity) - ent->next_type_entity->prev_type_entity = ent->prev_type_entity; - - ent->next_entity = 0; - ent->prev_entity = 0; - ent->next_type_entity = 0; - ent->prev_type_entity = 0; -} - -// -void game_world::snap(int snapping_client) -{ - for(entity *ent = first_entity; ent; ent = ent->next_entity) - ent->snap(snapping_client); -} - -void game_world::reset() -{ - // reset all entities - for(entity *ent = first_entity; ent; ent = ent->next_entity) - ent->reset(); - remove_entities(); - - for(entity *ent = first_entity; ent; ent = ent->next_entity) - ent->post_reset(); - remove_entities(); - - reset_requested = false; -} - -void game_world::remove_entities() -{ - // 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; - } -} - -void game_world::tick() -{ - if(reset_requested) - reset(); - - if(!paused) - { - // update all objects - for(entity *ent = first_entity; ent; ent = ent->next_entity) - ent->tick(); - - for(entity *ent = first_entity; ent; ent = ent->next_entity) - ent->tick_defered(); - } - - remove_entities(); -} - -////////////////////////////////////////////////// -// projectile -////////////////////////////////////////////////// -projectile::projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner, - int damage, int flags, float force, int sound_impact, int weapon) -: entity(OBJTYPE_PROJECTILE) -{ - this->type = type; - this->pos = pos; - this->vel = vel * SERVER_TICK_SPEED; // TODO: remove this - this->lifespan = span; - this->owner = owner; - this->powner = powner; - this->flags = flags; - this->force = force; - this->damage = damage; - this->sound_impact = sound_impact; - this->weapon = weapon; - this->bounce = 0; - this->start_tick = server_tick(); - world->insert_entity(this); -} - -void projectile::reset() -{ - world->destroy_entity(this); -} - -void projectile::tick() -{ - float gravity = -400; - if(type != WEAPON_ROCKET) - gravity = -100; - - float pt = (server_tick()-start_tick-1)/(float)SERVER_TICK_SPEED; - float ct = (server_tick()-start_tick)/(float)SERVER_TICK_SPEED; - vec2 prevpos = calc_pos(pos, vel, gravity, pt); - vec2 curpos = calc_pos(pos, vel, gravity, ct); - - lifespan--; - - int collide = col_check_point((int)curpos.x, (int)curpos.y); - - vec2 new_pos; - entity *targetplayer = (entity*)intersect_player(prevpos, curpos, new_pos, powner); - - if(targetplayer || collide || lifespan < 0 ) - { - if (lifespan >= 0 || weapon == WEAPON_ROCKET) - create_sound(pos, sound_impact); - - if (flags & PROJECTILE_FLAGS_EXPLODE) - create_explosion(prevpos, owner, weapon, false); - else if (targetplayer) - { - targetplayer->take_damage(normalize(vel) * max(0.001f, force), damage, owner, weapon); - } - - world->destroy_entity(this); - } -} - -void projectile::snap(int snapping_client) -{ - float ct = (server_tick()-start_tick)/(float)SERVER_TICK_SPEED; - vec2 curpos = calc_pos(pos, vel, -7.5f*SERVER_TICK_SPEED, ct); - - if(distance(players[snapping_client].pos, curpos) > 1000.0f) - return; - - 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->start_tick = start_tick; - proj->type = type; -} - -////////////////////////////////////////////////// -// player -////////////////////////////////////////////////// -// TODO: move to separate file -player::player() -: entity(OBJTYPE_PLAYER_CHARACTER) -{ - init(); -} - -void player::init() -{ - proximity_radius = phys_size; - client_id = -1; - team = -1; // -1 == spectator - extrapowerflags = 0; - - latency_accum = 0; - latency_accum_min = 0; - latency_accum_max = 0; - latency_avg = 0; - latency_min = 0; - latency_max = 0; - - reset(); -} - -void player::reset() -{ - pos = vec2(0.0f, 0.0f); - core.vel = vec2(0.0f, 0.0f); - //direction = vec2(0.0f, 1.0f); - score = 0; - dead = true; - clear_flag(entity::FLAG_PHYSICS); - spawning = false; - die_tick = 0; - damage_taken = 0; - state = STATE_UNKNOWN; - - mem_zero(&input, sizeof(input)); - mem_zero(&previnput, sizeof(previnput)); - num_inputs = 0; - - last_action = -1; - - emote_stop = 0; - damage_taken_tick = 0; - attack_tick = 0; - numobjectshit = 0; - ninjaactivationtick = 0; - currentmovetime = 0; - - active_weapon = WEAPON_GUN; - last_weapon = WEAPON_HAMMER; - wanted_weapon = WEAPON_GUN; -} - -void player::destroy() { } - -void player::set_weapon(int w) -{ - last_weapon = active_weapon; - active_weapon = w; - if(active_weapon < 0 || active_weapon >= NUM_WEAPONS) - active_weapon = 0; -} - -void player::respawn() -{ - spawning = true; -} - - -void player::set_team(int new_team) -{ - team = new_team; - die(client_id, -1); - - dbg_msg("game", "cid=%d team=%d", client_id, team); - - if(team == -1) - clear_flag(FLAG_PHYSICS); - else - set_flag(FLAG_PHYSICS); -} - - -bool try_spawntype(int t, vec2 *outpos) -{ - // get spawn point - int start, num; - map_get_type(t, &start, &num); - if(!num) - return false; - - int id = rand()%num; - mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + id, NULL, NULL); - *outpos = vec2((float)sp->x, (float)sp->y); - return true; -} - -void player::try_respawn() -{ - vec2 spawnpos = vec2(100.0f, -60.0f); - - // get spawn point - if(gameobj->gametype == GAMETYPE_CTF) - { - // try first try own team spawn, then normal spawn and then enemy - if(!try_spawntype(MAPRES_SPAWNPOINT_RED+(team&1), &spawnpos)) - { - if(!try_spawntype(MAPRES_SPAWNPOINT, &spawnpos)) - try_spawntype(MAPRES_SPAWNPOINT_RED+((team+1)&1), &spawnpos); - } - } - else - { - if(!try_spawntype(MAPRES_SPAWNPOINT, &spawnpos)) - try_spawntype(MAPRES_SPAWNPOINT_RED+(rand()&1), &spawnpos); - } - - // check if the position is occupado - entity *ents[2] = {0}; - int types[] = {OBJTYPE_PLAYER_CHARACTER}; - int num_ents = world->find_entities(spawnpos, 64, ents, 2, types, 1); - for(int i = 0; i < num_ents; i++) - { - if(ents[i] != this) - return; - } - - spawning = false; - pos = spawnpos; - - core.pos = pos; - core.vel = vec2(0,0); - core.hooked_player = -1; - - health = 10; - armor = 0; - jumped = 0; - dead = false; - set_flag(entity::FLAG_PHYSICS); - state = STATE_PLAYING; - - core.hook_state = HOOK_IDLE; - - mem_zero(&input, sizeof(input)); - - // init weapons - mem_zero(&weapons, sizeof(weapons)); - weapons[WEAPON_HAMMER].got = true; - weapons[WEAPON_HAMMER].ammo = -1; - weapons[WEAPON_GUN].got = true; - weapons[WEAPON_GUN].ammo = data->weapons[WEAPON_GUN].maxammo; - - //weapons[WEAPON_SNIPER].got = true; - //weapons[WEAPON_SNIPER].ammo = data->weapons[WEAPON_SNIPER].maxammo; - active_weapon = WEAPON_GUN; - last_weapon = WEAPON_HAMMER; - wanted_weapon = WEAPON_GUN; - - reload_timer = 0; - - // Create sound and spawn effects - create_sound(pos, SOUND_PLAYER_SPAWN); - create_spawn(pos); - - gameobj->on_player_spawn(this); -} - -bool player::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; -} - -struct input_count -{ - int presses; - int releases; -}; - -static input_count count_input(int prev, int cur) -{ - input_count c = {0,0}; - prev &= INPUT_STATE_MASK; - cur &= INPUT_STATE_MASK; - int i = prev; - while(i != cur) - { - i = (i+1)&INPUT_STATE_MASK; - if(i&1) - c.presses++; - else - c.releases++; - } - - return c; -} - - -int player::handle_ninja() -{ - vec2 direction = normalize(vec2(input.target_x, input.target_y)); - - if ((server_tick() - ninjaactivationtick) > (data->weapons[WEAPON_NINJA].duration * server_tickspeed() / 1000)) - { - // time's up, return - weapons[WEAPON_NINJA].got = false; - active_weapon = last_weapon; - set_weapon(active_weapon); - return 0; - } - - // Check if it should activate - if (count_input(previnput.fire, input.fire).presses && (server_tick() > currentcooldown)) - { - // ok then, activate ninja - attack_tick = server_tick(); - activationdir = direction; - currentmovetime = data->weapons[WEAPON_NINJA].movetime * server_tickspeed() / 1000; - currentcooldown = data->weapons[WEAPON_NINJA].firedelay * server_tickspeed() / 1000 + server_tick(); - - // reset hit objects - numobjectshit = 0; - - create_sound(pos, SOUND_NINJA_FIRE); - - // release all hooks when ninja is activated - //release_hooked(); - //release_hooks(); - } - - currentmovetime--; - - if (currentmovetime == 0) - { - // reset player velocity - core.vel *= 0.2f; - //return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; - } - - if (currentmovetime > 0) - { - // Set player velocity - core.vel = activationdir * data->weapons[WEAPON_NINJA].velocity; - vec2 oldpos = pos; - move_box(&core.pos, &core.vel, vec2(phys_size, phys_size), 0.0f); - // reset velocity so the client doesn't predict stuff - core.vel = vec2(0.0f,0.0f); - if ((currentmovetime % 2) == 0) - { - create_smoke(pos); - } - - // check if we hit anything along the way - { - int type = OBJTYPE_PLAYER_CHARACTER; - entity *ents[64]; - vec2 dir = pos - oldpos; - float radius = phys_size * 2.0f; //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] == this) - 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, pos) > (phys_size * 2.0f)) - continue; - - // hit a player, give him damage and stuffs... - create_sound(ents[i]->pos, SOUND_NINJA_HIT); - // set his velocity to fast upward (for now) - if(numobjectshit < 10) - hitobjects[numobjectshit++] = ents[i]; - ents[i]->take_damage(vec2(0,10.0f), data->weapons[WEAPON_NINJA].meleedamage, client_id,WEAPON_NINJA); - } - } - return MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION | MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY; - } - - return 0; -} - -int player::handle_sniper() -{ - return 0; - - struct input_count button = count_input(previnput.fire, input.fire); - if (button.releases) - { - vec2 direction = normalize(vec2(input.target_x, input.target_y)); - // Check if we were charging, if so fire - if (weapons[WEAPON_SNIPER].weaponstage >= WEAPONSTAGE_SNIPER_CHARGING) - { - new projectile(projectile::WEAPON_PROJECTILETYPE_SNIPER, - client_id, pos+vec2(0,0), direction*50.0f, - 100 + weapons[WEAPON_SNIPER].weaponstage * 20,this, weapons[WEAPON_SNIPER].weaponstage, 0, 0, -1, WEAPON_SNIPER); - create_sound(pos, SOUND_SNIPER_FIRE); - } - // Add blowback - core.vel = -direction * 10.0f * weapons[WEAPON_SNIPER].weaponstage; - - // update ammo and stuff - weapons[WEAPON_SNIPER].ammo = max(0,weapons[WEAPON_SNIPER].ammo - weapons[WEAPON_SNIPER].weaponstage); - weapons[WEAPON_SNIPER].weaponstage = WEAPONSTAGE_SNIPER_NEUTRAL; - weapons[WEAPON_SNIPER].chargetick = 0; - } - else if (input.fire & 1) - { - // Charge!! (if we are on the ground) - if (is_grounded() && weapons[WEAPON_SNIPER].ammo > 0) - { - if (!weapons[WEAPON_SNIPER].chargetick) - { - weapons[WEAPON_SNIPER].chargetick = server_tick(); - dbg_msg("game", "Chargetick='%d:'", server_tick()); - } - if ((server_tick() - weapons[WEAPON_SNIPER].chargetick) > server_tickspeed() * data->weapons[active_weapon].chargetime) - { - if (weapons[WEAPON_SNIPER].ammo > weapons[WEAPON_SNIPER].weaponstage) - { - weapons[WEAPON_SNIPER].weaponstage++; - weapons[WEAPON_SNIPER].chargetick = server_tick(); - } - else if ((server_tick() - weapons[WEAPON_SNIPER].chargetick) > server_tickspeed() * data->weapons[active_weapon].overchargetime) - { - // Ooopsie, weapon exploded - create_explosion(pos, client_id, WEAPON_SNIPER, false); - create_sound(pos, SOUND_ROCKET_EXPLODE); - // remove this weapon and change weapon to gun - weapons[WEAPON_SNIPER].got = false; - weapons[WEAPON_SNIPER].ammo = 0; - last_weapon = active_weapon; - active_weapon = WEAPON_GUN; - return 0; - } - } - - // While charging, don't move - return MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY|MODIFIER_RETURNFLAGS_NOHOOK; - } - else if (weapons[WEAPON_SNIPER].weaponstage) - weapons[WEAPON_SNIPER].weaponstage = WEAPONSTAGE_SNIPER_NEUTRAL; - } - return 0; -} - -int player::handle_weapons() -{ - vec2 direction = normalize(vec2(input.target_x, input.target_y)); - - if(config.dbg_stress) - { - for(int i = 0; i < NUM_WEAPONS; i++) - { - weapons[i].got = true; - weapons[i].ammo = 10; - } - - if(reload_timer) // twice as fast reload - reload_timer--; - } - - // check reload timer - if(reload_timer) - { - reload_timer--; - return 0; - } - - if (active_weapon == WEAPON_NINJA) - { - // don't update other weapons while ninja is active - return handle_ninja(); - } - - // select weapon - int next = count_input(previnput.next_weapon, input.next_weapon).presses; - int prev = count_input(previnput.prev_weapon, input.prev_weapon).presses; - - if(next < 128) // make sure we only try sane stuff - { - while(next) // next weapon selection - { - wanted_weapon = (wanted_weapon+1)%NUM_WEAPONS; - if(weapons[wanted_weapon].got) - next--; - } - } - - if(prev < 128) // make sure we only try sane stuff - { - while(prev) // prev weapon selection - { - wanted_weapon = (wanted_weapon-1)<0?NUM_WEAPONS-1:wanted_weapon-1; - if(weapons[wanted_weapon].got) - prev--; - } - } - - if(input.wanted_weapon) // direct weapon selection - wanted_weapon = input.wanted_weapon-1; - - if(wanted_weapon < 0 || wanted_weapon >= NUM_WEAPONS) - wanted_weapon = 0; - - // switch weapon if wanted - if(data->weapons[active_weapon].duration <= 0) - { - if(wanted_weapon != -1 && wanted_weapon != active_weapon && wanted_weapon >= 0 && wanted_weapon < NUM_WEAPONS && weapons[wanted_weapon].got) - { - if(active_weapon != wanted_weapon) - create_sound(pos, SOUND_WEAPON_SWITCH); - - set_weapon(wanted_weapon); - } - } - - if (active_weapon == WEAPON_SNIPER) - { - // don't update other weapons while sniper is active - return handle_sniper(); - } - - if(count_input(previnput.fire, input.fire).presses) //previnput.fire != input.fire && (input.fire&1)) - { - if(reload_timer == 0) - { - // fire! - if(weapons[active_weapon].ammo) - { - switch(active_weapon) - { - case WEAPON_HAMMER: - // reset objects hit - numobjectshit = 0; - create_sound(pos, SOUND_HAMMER_FIRE); - break; - - case WEAPON_GUN: - new projectile(WEAPON_GUN, - client_id, - pos+vec2(0,0), - direction*30.0f, - 100, - this, - 1, 0, 0, -1, WEAPON_GUN); - create_sound(pos, SOUND_GUN_FIRE); - break; - case WEAPON_ROCKET: - { - new projectile(WEAPON_ROCKET, - client_id, - pos+vec2(0,0), - direction*15.0f, - 100, - this, - 1, projectile::PROJECTILE_FLAGS_EXPLODE, 0, SOUND_ROCKET_EXPLODE, WEAPON_ROCKET); - create_sound(pos, SOUND_ROCKET_FIRE); - break; - } - case WEAPON_SHOTGUN: - { - int shotspread = 2; - for(int i = -shotspread; i <= shotspread; i++) - { - float spreading[] = {-0.12f, -0.05f, 0, 0.05f, 0.12f}; - float a = get_angle(direction); - float v = 1.0f-fabs(i/(float)shotspread); - a += spreading[i+2]; - new projectile(WEAPON_SHOTGUN, - client_id, - pos+vec2(0,0), - vec2(cosf(a), sinf(a))*(30.0f + 15.0f*v), - //vec2(cosf(a), sinf(a))*20.0f, - (int)(server_tickspeed()*0.3f), - this, - 1, 0, 0, -1, WEAPON_SHOTGUN); - } - create_sound(pos, SOUND_SHOTGUN_FIRE); - break; - } - } - - weapons[active_weapon].ammo--; - attack_tick = server_tick(); - reload_timer = data->weapons[active_weapon].firedelay * server_tickspeed() / 1000; - } - else - { - create_sound(pos, SOUND_WEAPON_NOAMMO); - } - } - } - - // Update weapons - if (active_weapon == WEAPON_HAMMER && reload_timer > 0) - { - // Handle collisions - // 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_CHARACTER; - entity *ents[64]; - vec2 lookdir(direction.x > 0.0f ? 1.0f : -1.0f, 0.0f); - vec2 dir = lookdir * data->weapons[active_weapon].meleereach; - float radius = length(dir * 0.5f); - vec2 center = 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] == this) - 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, pos) > (phys_size * 2.0f)) - continue; - - // hit a player, give him damage and stuffs... - // create sound for bash - //create_sound(ents[i]->pos, sound_impact); - vec2 fdir = normalize(ents[i]->pos- pos); - - // set his velocity to fast upward (for now) - create_smoke(ents[i]->pos); - create_sound(pos, SOUND_HAMMER_HIT); - if(numobjectshit < 10) - hitobjects[numobjectshit++] = ents[i]; - ents[i]->take_damage(vec2(0,-1.0f), data->weapons[active_weapon].meleedamage, client_id, active_weapon); - player* target = (player*)ents[i]; - vec2 dir; - if (length(target->pos - pos) > 0.0f) - dir = normalize(target->pos - pos); - else - dir = vec2(0,-1); - - target->core.vel += normalize(dir + vec2(0,-1.1f)) * 10.0f; - } - } - - if (data->weapons[active_weapon].ammoregentime) - { - // If equipped and not active, regen ammo? - if (reload_timer <= 0) - { - if (weapons[active_weapon].ammoregenstart < 0) - weapons[active_weapon].ammoregenstart = server_tick(); - - if ((server_tick() - weapons[active_weapon].ammoregenstart) >= data->weapons[active_weapon].ammoregentime * server_tickspeed() / 1000) - { - // Add some ammo - weapons[active_weapon].ammo = min(weapons[active_weapon].ammo + 1, data->weapons[active_weapon].maxammo); - weapons[active_weapon].ammoregenstart = -1; - } - } - else - { - weapons[active_weapon].ammoregenstart = -1; - } - } - - return 0; -} - -void player::tick() -{ - // check if we have enough input - // this is to prevent initial weird clicks - if(num_inputs < 2) - previnput = input; - - // do latency stuff - { - CLIENT_INFO info; - if(server_getclientinfo(client_id, &info)) - { - latency_accum += info.latency; - latency_accum_max = max(latency_accum_max, info.latency); - latency_accum_min = min(latency_accum_min, info.latency); - } - - if(server_tick()%server_tickspeed() == 0) - { - latency_avg = latency_accum/server_tickspeed(); - latency_max = latency_accum_max; - latency_min = latency_accum_min; - latency_accum = 0; - latency_accum_min = 1000; - latency_accum_max = 0; - } - } - - // enable / disable physics - if(team == -1 || dead) - world->core.players[client_id] = 0; - else - world->core.players[client_id] = &core; - - // spectator - if(team == -1) - return; - - if(spawning) - try_respawn(); - - // TODO: rework the input to be more robust - if(dead) - { - if(server_tick()-die_tick >= server_tickspeed()*5) // auto respawn after 3 sec - respawn(); - if((input.fire&1) && server_tick()-die_tick >= server_tickspeed()/2) // auto respawn after 0.5 sec - respawn(); - return; - } - - //player_core core; - //core.pos = pos; - //core.jumped = jumped; - core.input = input; - core.tick(); - - // handle weapons - handle_weapons(); - - state = input.state; - - // Previnput - previnput = input; - return; -} - -void player::tick_defered() -{ - if(!dead) - { - vec2 start_pos = core.pos; - vec2 start_vel = core.vel; - bool stuck_before = test_box(core.pos, vec2(28.0f, 28.0f)); - core.move(); - bool stuck_after_move = test_box(core.pos, vec2(28.0f, 28.0f)); - core.quantize(); - bool stuck_after_quant = test_box(core.pos, vec2(28.0f, 28.0f)); - pos = core.pos; - - if(!stuck_before && (stuck_after_move || stuck_after_quant)) - { - dbg_msg("player", "STUCK!!! %d %d %d %f %f %f %f %x %x %x %x", - stuck_before, - stuck_after_move, - stuck_after_quant, - start_pos.x, start_pos.y, - start_vel.x, start_vel.y, - *((unsigned *)&start_pos.x), *((unsigned *)&start_pos.y), - *((unsigned *)&start_vel.x), *((unsigned *)&start_vel.y)); - } - - - int events = core.triggered_events; - int mask = cmask_all_except_one(client_id); - - if(events&COREEVENT_GROUND_JUMP) create_sound(pos, SOUND_PLAYER_JUMP, mask); - if(events&COREEVENT_AIR_JUMP) - { - create_sound(pos, SOUND_PLAYER_JUMP, mask); - ev_common *c = (ev_common *)::events.create(EVENT_AIR_JUMP, sizeof(ev_common), mask); - if(c) - { - c->x = (int)pos.x; - c->y = (int)pos.y; - } - } - - //if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos); - if(events&COREEVENT_HOOK_ATTACH_PLAYER) create_sound(pos, SOUND_HOOK_ATTACH_PLAYER, mask); - if(events&COREEVENT_HOOK_ATTACH_GROUND) create_sound(pos, SOUND_HOOK_ATTACH_GROUND, mask); - //if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); - - } - - if(team == -1) - { - pos.x = input.target_x; - pos.y = input.target_y; - } -} - -void player::die(int killer, int weapon) -{ - int mode_special = gameobj->on_player_death(this, get_player(killer), weapon); - - dbg_msg("game", "kill killer='%d:%s' victim='%d:%s' weapon=%d special=%d", - killer, server_clientname(killer), - client_id, server_clientname(client_id), weapon, mode_special); - - // send the kill message - msg_pack_start(MSG_KILLMSG, MSGFLAG_VITAL); - msg_pack_int(killer); - msg_pack_int(client_id); - msg_pack_int(weapon); - msg_pack_int(mode_special); - msg_pack_end(); - server_send_msg(-1); - - // a nice sound - create_sound(pos, SOUND_PLAYER_DIE); - - // set dead state - dead = true; - die_tick = server_tick(); - clear_flag(FLAG_PHYSICS); - create_death(pos); -} - -bool player::take_damage(vec2 force, int dmg, int from, int weapon) -{ - core.vel += force; - - if(gameobj->is_friendly_fire(client_id, from) && !config.sv_teamdamage) - return false; - - // player only inflicts half damage on self - if(from == client_id) - dmg = max(1, dmg/2); - - // CTF and TDM (TODO: check for FF) - //if (gameobj->gametype != GAMETYPE_DM && from >= 0 && players[from].team == team) - //return false; - - damage_taken++; - - // create healthmod indicator - if(server_tick() < damage_taken_tick+25) - { - // make sure that the damage indicators doesn't group together - create_damageind(pos, damage_taken*0.25f, dmg); - } - else - { - damage_taken = 0; - create_damageind(pos, 0, dmg); - } - - if(armor) - { - armor -= 1; - dmg--; - } - - if(dmg > armor) - { - dmg -= armor; - armor = 0; - health -= dmg; - } - else - armor -= dmg; - - damage_taken_tick = server_tick(); - - // do damage hit sound - if(from >= 0) - create_sound(get_player(from)->pos, SOUND_HIT, cmask_one(from)); - - // check for death - if(health <= 0) - { - die(from, weapon); - - // set attacker's face to happy (taunt!) - if (from >= 0 && from != client_id) - { - player *p = get_player(from); - - p->emote_type = EMOTE_HAPPY; - p->emote_stop = server_tick() + server_tickspeed(); - } - - return false; - } - - if (dmg > 2) - create_sound(pos, SOUND_PLAYER_PAIN_LONG); - else - create_sound(pos, SOUND_PLAYER_PAIN_SHORT); - - emote_type = EMOTE_PAIN; - emote_stop = server_tick() + 500 * server_tickspeed() / 1000; - - // spawn blood? - return true; -} - -void player::snap(int snaping_client) -{ - if(1) - { - obj_player_info *info = (obj_player_info *)snap_new_item(OBJTYPE_PLAYER_INFO, client_id, sizeof(obj_player_info)); - - info->latency = latency_avg; - info->latency_flux = latency_max-latency_min; - info->local = 0; - info->clientid = client_id; - info->score = score; - info->team = team; - - if(client_id == snaping_client) - info->local = 1; - } - - if(health > 0 && distance(players[snaping_client].pos, pos) < 1000.0f) - { - obj_player_character *character = (obj_player_character *)snap_new_item(OBJTYPE_PLAYER_CHARACTER, client_id, sizeof(obj_player_character)); - - core.write(character); - - // this is to make sure that players that are just standing still - // isn't sent. this is because the physics keep bouncing between - // 0-128 when just standing. - // TODO: fix the physics so this isn't needed - if(snaping_client != client_id && abs(character->vy) < 256.0f) - character->vy = 0; - - if (emote_stop < server_tick()) - { - emote_type = EMOTE_NORMAL; - emote_stop = -1; - } - - character->emote = emote_type; - - character->ammocount = weapons[active_weapon].ammo; - character->health = 0; - character->armor = 0; - character->weapon = active_weapon; - character->weaponstage = weapons[active_weapon].weaponstage; - character->attacktick = attack_tick; - - if(client_id == snaping_client) - { - character->health = health; - character->armor = armor; - } - - if(dead) - character->health = -1; - - //if(length(vel) > 15.0f) - // player->emote = EMOTE_HAPPY; - - //if(damage_taken_tick+50 > server_tick()) - // player->emote = EMOTE_PAIN; - - if (character->emote == EMOTE_NORMAL) - { - if(250 - ((server_tick() - last_action)%(250)) < 5) - character->emote = EMOTE_BLINK; - } - - character->state = state; - } -} - -player *players; - -////////////////////////////////////////////////// -// powerup -////////////////////////////////////////////////// -powerup::powerup(int _type, int _subtype) -: entity(OBJTYPE_POWERUP) -{ - type = _type; - subtype = _subtype; - proximity_radius = phys_size; - - reset(); - - // TODO: should this be done here? - world->insert_entity(this); -} - -void powerup::reset() -{ - if (data->powerupinfo[type].startspawntime > 0) - spawntick = server_tick() + server_tickspeed() * data->powerupinfo[type].startspawntime; - else - spawntick = -1; -} - - -void send_weapon_pickup(int cid, int weapon); - -void powerup::tick() -{ - // wait for respawn - if(spawntick > 0) - { - if(server_tick() > spawntick) - { - // respawn - spawntick = -1; - - if(type == POWERUP_WEAPON) - create_sound(pos, SOUND_WEAPON_SPAWN); - } - else - return; - } - // 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 - int respawntime = -1; - switch (type) - { - case POWERUP_HEALTH: - if(pplayer->health < 10) - { - create_sound(pos, SOUND_PICKUP_HEALTH); - pplayer->health = min(10, pplayer->health + data->powerupinfo[type].amount); - respawntime = data->powerupinfo[type].respawntime; - } - break; - case POWERUP_ARMOR: - if(pplayer->armor < 10) - { - create_sound(pos, SOUND_PICKUP_ARMOR); - pplayer->armor = min(10, pplayer->armor + data->powerupinfo[type].amount); - respawntime = data->powerupinfo[type].respawntime; - } - break; - - case POWERUP_WEAPON: - if(subtype >= 0 && subtype < NUM_WEAPONS) - { - if(pplayer->weapons[subtype].ammo < 10 || !pplayer->weapons[subtype].got) - { - pplayer->weapons[subtype].got = true; - pplayer->weapons[subtype].ammo = min(10, pplayer->weapons[subtype].ammo + data->powerupinfo[type].amount); - respawntime = data->powerupinfo[type].respawntime; - - // TODO: data compiler should take care of stuff like this - if(subtype == WEAPON_ROCKET) - create_sound(pos, SOUND_PICKUP_ROCKET); - else if(subtype == WEAPON_SHOTGUN) - create_sound(pos, SOUND_PICKUP_SHOTGUN); - - send_weapon_pickup(pplayer->client_id, subtype); - } - } - break; - case POWERUP_NINJA: - { - // activate ninja on target player - pplayer->ninjaactivationtick = server_tick(); - pplayer->weapons[WEAPON_NINJA].got = true; - pplayer->last_weapon = pplayer->active_weapon; - pplayer->active_weapon = WEAPON_NINJA; - respawntime = data->powerupinfo[type].respawntime; - create_sound(pos, SOUND_PICKUP_NINJA); - - // loop through all players, setting their emotes - entity *ents[64]; - const int types[] = {OBJTYPE_PLAYER_CHARACTER}; - int num = world->find_entities(vec2(0, 0), 1000000, ents, 64, types, 1); - for (int i = 0; i < num; i++) - { - player *p = (player *)ents[i]; - if (p != pplayer) - { - p->emote_type = EMOTE_SURPRISE; - p->emote_stop = server_tick() + server_tickspeed(); - } - } - - pplayer->emote_type = EMOTE_ANGRY; - pplayer->emote_stop = server_tick() + 1200 * server_tickspeed() / 1000; - - break; - } - default: - break; - }; - - if(respawntime >= 0) - { - dbg_msg("game", "pickup player='%d:%s' item=%d/%d", - pplayer->client_id, server_clientname(pplayer->client_id), type, subtype); - spawntick = server_tick() + server_tickspeed() * respawntime; - } - } -} - -void powerup::snap(int snapping_client) -{ - if(spawntick != -1) - return; - - obj_powerup *up = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup)); - up->x = (int)pos.x; - up->y = (int)pos.y; - up->type = type; // TODO: two diffrent types? what gives? - up->subtype = subtype; -} - -// POWERUP END /////////////////////// - -player *get_player(int index) -{ - return &players[index]; -} - -void create_damageind(vec2 p, float angle, int amount) -{ - float a = 3 * 3.14159f / 2 + angle; - //float a = get_angle(dir); - float s = a-pi/3; - float e = a+pi/3; - for(int i = 0; i < amount; i++) - { - float f = mix(s, e, float(i+1)/float(amount+2)); - ev_damageind *ev = (ev_damageind *)events.create(EVENT_DAMAGEINDICATION, sizeof(ev_damageind)); - if(ev) - { - ev->x = (int)p.x; - ev->y = (int)p.y; - ev->angle = (int)(f*256.0f); - } - } -} - -void create_explosion(vec2 p, int owner, int weapon, bool bnodamage) -{ - // create the event - ev_explosion *ev = (ev_explosion *)events.create(EVENT_EXPLOSION, sizeof(ev_explosion)); - if(ev) - { - ev->x = (int)p.x; - ev->y = (int)p.y; - } - - if (!bnodamage) - { - // deal damage - entity *ents[64]; - const float radius = 128.0f; - const float innerradius = 42.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); - float l = length(diff); - if(l) - forcedir = normalize(diff); - l = 1-clamp((l-innerradius)/(radius-innerradius), 0.0f, 1.0f); - float dmg = 6 * l; - if((int)dmg) - ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner, weapon); - } - } -} - -void create_smoke(vec2 p) -{ - // create the event - ev_explosion *ev = (ev_explosion *)events.create(EVENT_SMOKE, sizeof(ev_explosion)); - if(ev) - { - ev->x = (int)p.x; - ev->y = (int)p.y; - } -} - -void create_spawn(vec2 p) -{ - // create the event - ev_spawn *ev = (ev_spawn *)events.create(EVENT_SPAWN, sizeof(ev_spawn)); - if(ev) - { - ev->x = (int)p.x; - ev->y = (int)p.y; - } -} - -void create_death(vec2 p) -{ - // create the event - ev_death *ev = (ev_death *)events.create(EVENT_DEATH, sizeof(ev_death)); - if(ev) - { - ev->x = (int)p.x; - ev->y = (int)p.y; - } -} - -void create_sound(vec2 pos, int sound, int mask) -{ - if (sound < 0) - return; - - // create a sound - ev_sound *ev = (ev_sound *)events.create(EVENT_SOUND_WORLD, sizeof(ev_sound), mask); - if(ev) - { - ev->x = (int)pos.x; - ev->y = (int)pos.y; - ev->sound = sound; - } -} - -void create_sound_global(int sound, int target) -{ - if (sound < 0) - return; - - msg_pack_start(MSG_SOUND_GLOBAL, MSGFLAG_VITAL); - msg_pack_int(sound); - server_send_msg(-1); -} - -// TODO: should be more general -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; - const int types[] = {OBJTYPE_PLAYER_CHARACTER}; - int num = world->find_entities(center, radius, ents, 64, types, 1); - for (int i = 0; i < num; i++) - { - // Check if entity is a player - if (ents[i] != notthis) - { - new_pos = ents[i]->pos; - return (player*)ents[i]; - } - } - - return 0; -} - - -void send_chat(int cid, int team, const char *msg) -{ - if(cid >= 0 && cid < MAX_CLIENTS) - dbg_msg("chat", "%d:%d:%s: %s", cid, team, server_clientname(cid), msg); - else - dbg_msg("chat", "*** %s", msg); - - if(team == -1) - { - msg_pack_start(MSG_CHAT, MSGFLAG_VITAL); - msg_pack_int(cid); - msg_pack_int(0); - msg_pack_string(msg, 512); - msg_pack_end(); - server_send_msg(-1); - } - else - { - msg_pack_start(MSG_CHAT, MSGFLAG_VITAL); - msg_pack_int(cid); - msg_pack_int(1); - msg_pack_string(msg, 512); - msg_pack_end(); - - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1 && players[i].team == team) - server_send_msg(i); - } - } -} - - -// Server hooks -void mods_tick() -{ - // clear all events - events.clear(); - world->tick(); - - if(world->paused) // make sure that the game object always updates - gameobj->tick(); - - if(config.sv_restart) - { - if(config.sv_restart > 1) - gameobj->do_warmup(config.sv_restart); - else - gameobj->startround(); - - config.sv_restart = 0; - } - - if(config.sv_msg[0] != 0) - { - send_chat(-1, 0, config.sv_msg); - config.sv_msg[0] = 0; - } -} - -void mods_snap(int client_id) -{ - world->snap(client_id); - events.snap(client_id); -} - -void mods_client_input(int client_id, void *input) -{ - if(!world->paused) - { - if (memcmp(&players[client_id].input, input, sizeof(player_input)) != 0) - players[client_id].last_action = server_tick(); - - //players[client_id].previnput = players[client_id].input; - players[client_id].input = *(player_input*)input; - players[client_id].num_inputs++; - } -} - -void send_info(int who, int to_who) -{ - msg_pack_start(MSG_SETINFO, MSGFLAG_VITAL); - msg_pack_int(who); - msg_pack_string(server_clientname(who), 64); - msg_pack_string(players[who].skin_name, 64); - msg_pack_int(players[who].use_custom_color); - msg_pack_int(players[who].color_body); - msg_pack_int(players[who].color_feet); - msg_pack_end(); - server_send_msg(to_who); -} - -void send_emoticon(int cid, int emoticon) -{ - msg_pack_start(MSG_EMOTICON, MSGFLAG_VITAL); - msg_pack_int(cid); - msg_pack_int(emoticon % 16); - msg_pack_end(); - server_send_msg(-1); -} - -void send_weapon_pickup(int cid, int weapon) -{ - msg_pack_start(MSG_WEAPON_PICKUP, MSGFLAG_VITAL); - msg_pack_int(weapon); - msg_pack_end(); - server_send_msg(cid); -} - -void mods_client_enter(int client_id) -{ - world->insert_entity(&players[client_id]); - players[client_id].respawn(); - dbg_msg("game", "join player='%d:%s'", client_id, server_clientname(client_id)); - - char buf[512]; - sprintf(buf, "%s has joined the game", server_clientname(client_id)); - send_chat(-1, -1, buf); -} - -void mods_connected(int client_id) -{ - players[client_id].init(); - players[client_id].client_id = client_id; - - //dbg_msg("game", "connected player='%d:%s'", client_id, server_clientname(client_id)); - - // Check which team the player should be on - if(gameobj->gametype == GAMETYPE_DM) - players[client_id].team = 0; - else - players[client_id].team = gameobj->getteam(client_id); -} - -void mods_client_drop(int client_id) -{ - char buf[512]; - sprintf(buf, "%s has left the game", server_clientname(client_id)); - send_chat(-1, -1, buf); - - dbg_msg("game", "leave player='%d:%s'", client_id, server_clientname(client_id)); - - gameobj->on_player_death(&players[client_id], 0, -1); - world->remove_entity(&players[client_id]); - world->core.players[client_id] = 0x0; - players[client_id].client_id = -1; -} - -void mods_message(int msg, int client_id) -{ - if(msg == MSG_SAY) - { - int team = msg_unpack_int(); - const char *text = msg_unpack_string(); - if(team) - team = players[client_id].team; - else - team = -1; - send_chat(client_id, team, text); - } - else if (msg == MSG_SETTEAM) - { - // Switch team on given client and kill/respawn him - players[client_id].set_team(msg_unpack_int()); - gameobj->on_player_info_change(&players[client_id]); - - // send all info to this client - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1) - send_info(i, -1); - } - } - else if (msg == MSG_CHANGEINFO || msg == MSG_STARTINFO) - { - const char *name = msg_unpack_string(); - const char *skin_name = msg_unpack_string(); - players[client_id].use_custom_color = msg_unpack_int(); - players[client_id].color_body = msg_unpack_int(); - players[client_id].color_feet = msg_unpack_int(); - - // check for invalid chars - const char *p = name; - while (*p) - { - if(*p < 32) - return; - p++; - } - - // - if(msg == MSG_CHANGEINFO && strcmp(name, server_clientname(client_id)) != 0) - { - char msg[256]; - sprintf(msg, "*** %s changed name to %s", server_clientname(client_id), name); - send_chat(-1, -1, msg); - } - - //send_set_name(client_id, players[client_id].name, name); - strncpy(players[client_id].skin_name, skin_name, 64); - server_setclientname(client_id, name); - - gameobj->on_player_info_change(&players[client_id]); - - if(msg == MSG_STARTINFO) - { - // send all info to this client - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1) - send_info(i, client_id); - } - - msg_pack_start(MSG_READY_TO_ENTER, MSGFLAG_VITAL); - msg_pack_end(); - server_send_msg(client_id); - } - - send_info(client_id, -1); - } - else if (msg == MSG_EMOTICON) - { - int emoteicon = msg_unpack_int(); - send_emoticon(client_id, emoteicon % 16); - } -} - -extern unsigned char internal_data[]; - -void mods_init() -{ - if(!data) /* only load once */ - data = load_data_from_memory(internal_data); - - col_init(32); - - world = new game_world; - players = new player[MAX_CLIENTS]; - - // select gametype - if(strcmp(config.sv_gametype, "ctf") == 0) - gameobj = new gameobject_ctf; - else if(strcmp(config.sv_gametype, "tdm") == 0) - gameobj = new gameobject_tdm; - else - gameobj = new gameobject_dm; - - // setup core world - for(int i = 0; i < MAX_CLIENTS; i++) - players[i].core.world = &world->core; - - // - int start, num; - map_get_type(MAPRES_ITEM, &start, &num); - - // TODO: this is way more complicated then it should be - for(int i = 0; i < num; i++) - { - mapres_item *it = (mapres_item *)map_get_item(start+i, 0, 0); - - int type = -1; - int subtype = 0; - - switch(it->type) - { - case ITEM_WEAPON_GUN: - type = POWERUP_WEAPON; - subtype = WEAPON_GUN; - break; - case ITEM_WEAPON_SHOTGUN: - type = POWERUP_WEAPON; - subtype = WEAPON_SHOTGUN; - break; - case ITEM_WEAPON_ROCKET: - type = POWERUP_WEAPON; - subtype = WEAPON_ROCKET; - break; - case ITEM_WEAPON_HAMMER: - type = POWERUP_WEAPON; - subtype = WEAPON_HAMMER; - break; - - case ITEM_HEALTH: - type = POWERUP_HEALTH; - break; - - case ITEM_ARMOR: - type = POWERUP_ARMOR; - break; - - case ITEM_NINJA: - if(config.sv_powerups) - { - type = POWERUP_NINJA; - subtype = WEAPON_NINJA; - } - break; - }; - - if(type != -1) - { - // LOL, the only new in the entire game code - // perhaps we can get rid of it. seems like a stupid thing to have - powerup *ppower = new powerup(type, subtype); - ppower->pos = vec2(it->x, it->y); - } - } - - if(gameobj->gametype == GAMETYPE_CTF) - { - } - - world->insert_entity(gameobj); - - - if(config.dbg_bots) - { - - for(int i = 0; i < config.dbg_bots ; i++) - { - mods_connected(MAX_CLIENTS-i-1); - mods_client_enter(MAX_CLIENTS-i-1); - if(gameobj->gametype != GAMETYPE_DM) - players[MAX_CLIENTS-i-1].team = i&1; - } - } -} - -void mods_shutdown() -{ - delete [] players; - delete gameobj; - delete world; - gameobj = 0; - players = 0; - world = 0; -} - -void mods_presnap() {} -void mods_postsnap() {} - -extern "C" const char *mods_net_version() { return TEEWARS_NETVERSION; } diff --git a/src/game/server/game_server.h b/src/game/server/game_server.h deleted file mode 100644 index 65ef58f4..00000000 --- a/src/game/server/game_server.h +++ /dev/null @@ -1,2 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - diff --git a/src/game/server/gs_common.cpp b/src/game/server/gs_common.cpp new file mode 100644 index 00000000..dc0d5c05 --- /dev/null +++ b/src/game/server/gs_common.cpp @@ -0,0 +1,264 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "gs_common.h" +#include + +gameobject::gameobject() +: entity(OBJTYPE_GAME) +{ + // select gametype + if(strcmp(config.sv_gametype, "ctf") == 0) + { + gametype = GAMETYPE_CTF; + dbg_msg("game", "-- Capture The Flag --"); + } + else if(strcmp(config.sv_gametype, "tdm") == 0) + { + gametype = GAMETYPE_TDM; + dbg_msg("game", "-- Team Death Match --"); + } + else + { + gametype = GAMETYPE_DM; + dbg_msg("game", "-- Death Match --"); + } + + // + do_warmup(config.sv_warmup); + game_over_tick = -1; + sudden_death = 0; + round_start_tick = server_tick(); + round_count = 0; + is_teamplay = false; + teamscore[0] = 0; + teamscore[1] = 0; +} + +void gameobject::endround() +{ + if(warmup) // game can't end when we are running warmup + return; + + world->paused = true; + game_over_tick = server_tick(); + sudden_death = 0; +} + +void gameobject::resetgame() +{ + world->reset_requested = true; +} + +static bool is_separator(char c) +{ + return c == ';' || c == ' ' || c == ',' || c == '\t'; +} + +void gameobject::startround() +{ + resetgame(); + + round_start_tick = server_tick(); + sudden_death = 0; + game_over_tick = -1; + world->paused = false; + teamscore[0] = 0; + teamscore[1] = 0; + round_count++; +} + +void gameobject::cyclemap() +{ + if(!strlen(config.sv_maprotation)) + return; + // handle maprotation + char buf[512]; + const char *s = strstr(config.sv_maprotation, config.sv_map); + if(s == 0) + s = config.sv_maprotation; // restart rotation + else + { + s += strlen(config.sv_map); // skip this map + while(is_separator(s[0])) + s++; + if(s[0] == 0) + s = config.sv_maprotation; // restart rotation + } + + int i = 0; + for(; i < 512; i++) + { + buf[i] = s[i]; + if(is_separator(s[i]) || s[i] == 0) + { + buf[i] = 0; + break; + } + } + + i = 0; // skip spaces + while(is_separator(buf[i])) + i++; + + dbg_msg("game", "rotating map to %s", &buf[i]); + strcpy(config.sv_map, &buf[i]); +} + +void gameobject::post_reset() +{ + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1) + players[i].respawn(); + } +} + +void gameobject::on_player_info_change(class player *p) +{ + const int team_colors[2] = {65387, 10223467}; + if(is_teamplay) + { + if(p->team >= 0 || p->team <= 1) + { + p->use_custom_color = 1; + p->color_body = team_colors[p->team]; + p->color_feet = team_colors[p->team]; + } + } +} + + +int gameobject::on_player_death(class player *victim, class player *killer, int weapon) +{ + // do scoreing + if(!killer) + return 0; + if(killer == victim) + victim->score--; // suicide + else + { + if(is_teamplay && victim->team == killer->team) + killer->score--; // teamkill + else + killer->score++; // normal kill + } + return 0; +} + +void gameobject::do_warmup(int seconds) +{ + warmup = seconds*SERVER_TICK_SPEED; +} + +bool gameobject::is_friendly_fire(int cid1, int cid2) +{ + if(cid1 == cid2) + return false; + + if(is_teamplay) + { + if(players[cid1].team == players[cid2].team) + return true; + } + + return false; +} + +void gameobject::tick() +{ + // do warmup + if(warmup) + { + warmup--; + if(!warmup) + startround(); + } + + if(game_over_tick != -1) + { + // game over.. wait for restart + if(server_tick() > game_over_tick+server_tickspeed()*10) + { + cyclemap(); + startround(); + } + } + + + // update browse info + int prog = -1; + if(config.sv_timelimit > 0) + prog = max(prog, (server_tick()-round_start_tick) * 100 / (config.sv_timelimit*server_tickspeed()*60)); + + if(config.sv_scorelimit) + { + if(is_teamplay) + { + prog = max(prog, (teamscore[0]*100)/config.sv_scorelimit); + prog = max(prog, (teamscore[1]*100)/config.sv_scorelimit); + } + else + { + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1) + prog = max(prog, (players[i].score*100)/config.sv_scorelimit); + } + } + } + + if(warmup) + prog = -1; + + server_setbrowseinfo(gametype, prog); +} + +void gameobject::snap(int snapping_client) +{ + obj_game *game = (obj_game *)snap_new_item(OBJTYPE_GAME, 0, sizeof(obj_game)); + game->paused = world->paused; + game->game_over = game_over_tick==-1?0:1; + game->sudden_death = sudden_death; + + game->score_limit = config.sv_scorelimit; + game->time_limit = config.sv_timelimit; + game->round_start_tick = round_start_tick; + game->gametype = gametype; + + game->warmup = warmup; + + game->teamscore[0] = teamscore[0]; + game->teamscore[1] = teamscore[1]; +} + +int gameobject::getteam(int notthisid) +{ + int numplayers[2] = {0,0}; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1 && players[i].client_id != notthisid) + { + numplayers[players[i].team]++; + } + } + + return numplayers[0] > numplayers[1] ? 1 : 0; +} + +void gameobject::do_team_wincheck() +{ + if(game_over_tick == -1 && !warmup) + { + // check score win condition + if((config.sv_scorelimit > 0 && (teamscore[0] >= config.sv_scorelimit || teamscore[1] >= config.sv_scorelimit)) || + (config.sv_timelimit > 0 && (server_tick()-round_start_tick) >= config.sv_timelimit*server_tickspeed()*60)) + { + if(teamscore[0] != teamscore[1]) + endround(); + else + sudden_death = 1; + } + } +} + +gameobject *gameobj = 0; diff --git a/src/game/server/gs_common.h b/src/game/server/gs_common.h new file mode 100644 index 00000000..93264f9f --- /dev/null +++ b/src/game/server/gs_common.h @@ -0,0 +1,327 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include "../g_game.h" +#include "../generated/gs_data.h" + + +void create_sound_global(int sound, int target=-1); + +inline int cmask_all() { return -1; } +inline int cmask_one(int cid) { return 1<stand_pos = vec2(stand->x, stand->y); + f->pos = f->stand_pos; + flags[i] = f; + //dbg_msg("game", "flag at %f,%f", f->pos.x, f->pos.y); + } + else + { + // report massive failure + flags[i] = 0; + } + } + + is_teamplay = true; +} + +void gameobject_ctf::on_player_spawn(class player *p) +{ +} + +int gameobject_ctf::on_player_death(class player *victim, class player *killer, int weaponid) +{ + gameobject::on_player_death(victim, killer, weaponid); + int had_flag = 0; + + // drop flags + for(int fi = 0; fi < 2; fi++) + { + flag *f = flags[fi]; + if(f && f->carrying_player == killer) + had_flag |= 2; + if(f && f->carrying_player == victim) + { + create_sound_global(SOUND_CTF_DROP); + f->drop_tick = server_tick(); + f->carrying_player = 0; + f->vel = vec2(0,0); + + if(killer && killer->team != victim->team) + killer->score++; + + had_flag |= 1; + } + } + + return had_flag; +} + +void gameobject_ctf::tick() +{ + gameobject::tick(); + + do_team_wincheck(); + + // do flags + for(int fi = 0; fi < 2; fi++) + { + flag *f = flags[fi]; + + if(!f) + continue; + + // + if(f->carrying_player) + { + // update flag position + f->pos = f->carrying_player->pos; + + if(flags[fi^1]->at_stand) + { + if(distance(f->pos, flags[fi^1]->pos) < 24) + { + // CAPTURE! \o/ + teamscore[fi^1] += 100; + f->carrying_player->score += 5; + for(int i = 0; i < 2; i++) + flags[i]->reset(); + + dbg_msg("", "capture sound %d", SOUND_CTF_CAPTURE); + create_sound_global(SOUND_CTF_CAPTURE); + } + } + } + else + { + player *players[MAX_CLIENTS]; + int types[] = {OBJTYPE_PLAYER_CHARACTER}; + int num = world->find_entities(f->pos, 32.0f, (entity**)players, MAX_CLIENTS, types, 1); + for(int i = 0; i < num; i++) + { + if(players[i]->team == f->team) + { + // return the flag + if(!f->at_stand) + { + players[i]->score += 1; + create_sound_global(SOUND_CTF_RETURN); + f->reset(); + } + } + else + { + // take the flag + if(f->at_stand) + teamscore[fi^1]++; + f->at_stand = 0; + f->carrying_player = players[i]; + f->carrying_player->score += 1; + create_sound_global(SOUND_CTF_GRAB); + break; + } + } + + if(!f->carrying_player && !f->at_stand) + { + if(server_tick() > f->drop_tick + SERVER_TICK_SPEED*30) + { + create_sound_global(SOUND_CTF_RETURN); + f->reset(); + } + else + { + f->vel.y += gravity; + move_box(&f->pos, &f->vel, vec2(f->phys_size, f->phys_size), 0.5f); + } + } + } + } +} + +// Flag +flag::flag(int _team) +: entity(OBJTYPE_FLAG) +{ + team = _team; + proximity_radius = phys_size; + carrying_player = 0x0; + + reset(); + + // TODO: should this be done here? + world->insert_entity(this); +} + +void flag::reset() +{ + carrying_player = 0; + at_stand = 1; + pos = stand_pos; + vel = vec2(0,0); +} + +void flag::snap(int snapping_client) +{ + obj_flag *flag = (obj_flag *)snap_new_item(OBJTYPE_FLAG, team, sizeof(obj_flag)); + flag->x = (int)pos.x; + flag->y = (int)pos.y; + flag->team = team; + flag->carried_by = -1; + + if(at_stand) + flag->carried_by = -2; + else if(carrying_player) + flag->carried_by = carrying_player->client_id; +} diff --git a/src/game/server/gs_game_ctf.h b/src/game/server/gs_game_ctf.h new file mode 100644 index 00000000..02acef37 --- /dev/null +++ b/src/game/server/gs_game_ctf.h @@ -0,0 +1,33 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +// game object +class gameobject_ctf : public gameobject +{ +public: + class flag *flags[2]; + + gameobject_ctf(); + virtual void tick(); + + virtual void on_player_spawn(class player *p); + virtual int on_player_death(class player *victim, class player *killer, int weapon); +}; + +// TODO: move to seperate file +class flag : public entity +{ +public: + static const int phys_size = 14; + player *carrying_player; + vec2 vel; + vec2 stand_pos; + + int team; + int at_stand; + int drop_tick; + + flag(int _team); + + virtual void reset(); + virtual void snap(int snapping_client); +}; diff --git a/src/game/server/gs_game_dm.cpp b/src/game/server/gs_game_dm.cpp new file mode 100644 index 00000000..98317578 --- /dev/null +++ b/src/game/server/gs_game_dm.cpp @@ -0,0 +1,42 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "gs_common.h" +#include "gs_game_dm.h" + +void gameobject_dm::tick() +{ + if(game_over_tick == -1) + { + // game is running + + // gather some stats + int topscore = 0; + int topscore_count = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1) + { + if(players[i].score > topscore) + { + topscore = players[i].score; + topscore_count = 1; + } + else if(players[i].score == topscore) + topscore_count++; + } + } + + // check score win condition + if((config.sv_scorelimit > 0 && topscore >= config.sv_scorelimit) || + (config.sv_timelimit > 0 && (server_tick()-round_start_tick) >= config.sv_timelimit*server_tickspeed()*60)) + { + if(topscore_count == 1) + endround(); + else + sudden_death = 1; + } + } + + gameobject::tick(); +} + diff --git a/src/game/server/gs_game_dm.h b/src/game/server/gs_game_dm.h new file mode 100644 index 00000000..96bff3ae --- /dev/null +++ b/src/game/server/gs_game_dm.h @@ -0,0 +1,7 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +// game object +class gameobject_dm : public gameobject +{ +public: + virtual void tick(); +}; diff --git a/src/game/server/gs_game_tdm.cpp b/src/game/server/gs_game_tdm.cpp new file mode 100644 index 00000000..7aa12e2b --- /dev/null +++ b/src/game/server/gs_game_tdm.cpp @@ -0,0 +1,32 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include "gs_common.h" +#include "gs_game_tdm.h" + +gameobject_tdm::gameobject_tdm() +{ + is_teamplay = true; +} + + +int gameobject_tdm::on_player_death(class player *victim, class player *killer, int weapon) +{ + gameobject::on_player_death(victim, killer, weapon); + + if(weapon >= 0) + { + // do team scoring + if(killer == victim) + teamscore[killer->team&1]--; // klant arschel + else + teamscore[killer->team&1]++; // good shit + } + return 0; +} + +void gameobject_tdm::tick() +{ + do_team_wincheck(); + + gameobject::tick(); +} diff --git a/src/game/server/gs_game_tdm.h b/src/game/server/gs_game_tdm.h new file mode 100644 index 00000000..70b4646e --- /dev/null +++ b/src/game/server/gs_game_tdm.h @@ -0,0 +1,10 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +// game object +class gameobject_tdm : public gameobject +{ +public: + gameobject_tdm(); + + int on_player_death(class player *victim, class player *killer, int weapon); + virtual void tick(); +}; diff --git a/src/game/server/gs_server.cpp b/src/game/server/gs_server.cpp new file mode 100644 index 00000000..65985371 --- /dev/null +++ b/src/game/server/gs_server.cpp @@ -0,0 +1,1859 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include +#include "../g_version.h" +#include "gs_common.h" +#include "gs_game_ctf.h" +#include "gs_game_tdm.h" +#include "gs_game_dm.h" + +data_container *data = 0x0; + +class player* get_player(int index); +void create_damageind(vec2 p, float angle_mod, int amount); +void create_explosion(vec2 p, int owner, int weapon, bool bnodamage); +void create_smoke(vec2 p); +void create_spawn(vec2 p); +void create_death(vec2 p); +void create_sound(vec2 pos, int sound, int mask=-1); +class player *intersect_player(vec2 pos0, vec2 pos1, vec2 &new_pos, class entity *notthis = 0); + +game_world *world; + +////////////////////////////////////////////////// +// Event handler +////////////////////////////////////////////////// +event_handler::event_handler() +{ + clear(); +} + +void *event_handler::create(int type, int size, int mask) +{ + if(num_events == MAX_EVENTS) + return 0; + if(current_offset+size >= MAX_DATASIZE) + return 0; + + void *p = &data[current_offset]; + offsets[num_events] = current_offset; + types[num_events] = type; + sizes[num_events] = size; + client_masks[num_events] = mask; + current_offset += size; + num_events++; + return p; +} + +void event_handler::clear() +{ + num_events = 0; + current_offset = 0; +} + +void event_handler::snap(int snapping_client) +{ + for(int i = 0; i < num_events; i++) + { + if(cmask_is_set(client_masks[i], snapping_client)) + { + ev_common *ev = (ev_common *)&data[offsets[i]]; + if(distance(players[snapping_client].pos, vec2(ev->x, ev->y)) < 1500.0f) + { + void *d = snap_new_item(types[i], i, sizes[i]); + mem_copy(d, &data[offsets[i]], sizes[i]); + } + } + } +} + +event_handler events; + +////////////////////////////////////////////////// +// Entity +////////////////////////////////////////////////// +entity::entity(int objtype) +{ + this->objtype = objtype; + pos = vec2(0,0); + flags = FLAG_PHYSICS; + proximity_radius = 0; + + id = snap_new_id(); + + next_entity = 0; + prev_entity = 0; + prev_type_entity = 0; + next_type_entity = 0; +} + +entity::~entity() +{ + snap_free_id(id); +} + +////////////////////////////////////////////////// +// game world +////////////////////////////////////////////////// +game_world::game_world() +{ + paused = false; + reset_requested = false; + first_entity = 0x0; + for(int i = 0; i < NUM_ENT_TYPES; i++) + first_entity_types[i] = 0; +} + +int game_world::find_entities(vec2 pos, float radius, entity **ents, int max) +{ + int num = 0; + for(entity *ent = first_entity; ent; ent = ent->next_entity) + { + if(!(ent->flags&entity::FLAG_PHYSICS)) + continue; + + if(distance(ent->pos, pos) < radius+ent->proximity_radius) + { + ents[num] = ent; + num++; + if(num == max) + break; + } + } + + return num; +} + +int game_world::find_entities(vec2 pos, float radius, entity **ents, int max, const int* types, int maxtypes) +{ + int num = 0; + for(int t = 0; t < maxtypes; t++) + { + for(entity *ent = first_entity_types[types[t]]; ent; ent = ent->next_type_entity) + { + if(!(ent->flags&entity::FLAG_PHYSICS)) + continue; + + if(distance(ent->pos, pos) < radius+ent->proximity_radius) + { + ents[num] = ent; + num++; + if(num == max) + break; + } + } + } + + return num; +} + +void game_world::insert_entity(entity *ent) +{ + entity *cur = first_entity; + while(cur) + { + dbg_assert(cur != ent, "err"); + cur = cur->next_entity; + } + + // insert it + if(first_entity) + first_entity->prev_entity = ent; + ent->next_entity = first_entity; + ent->prev_entity = 0x0; + first_entity = ent; + + // into typelist aswell + if(first_entity_types[ent->objtype]) + first_entity_types[ent->objtype]->prev_type_entity = ent; + ent->next_type_entity = first_entity_types[ent->objtype]; + ent->prev_type_entity = 0x0; + first_entity_types[ent->objtype] = ent; +} + +void game_world::destroy_entity(entity *ent) +{ + ent->set_flag(entity::FLAG_DESTROY); +} + +void game_world::remove_entity(entity *ent) +{ + // not in the list + if(!ent->next_entity && !ent->prev_entity && first_entity != ent) + return; + + // 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; + + if(ent->prev_type_entity) + ent->prev_type_entity->next_type_entity = ent->next_type_entity; + else + first_entity_types[ent->objtype] = ent->next_type_entity; + if(ent->next_type_entity) + ent->next_type_entity->prev_type_entity = ent->prev_type_entity; + + ent->next_entity = 0; + ent->prev_entity = 0; + ent->next_type_entity = 0; + ent->prev_type_entity = 0; +} + +// +void game_world::snap(int snapping_client) +{ + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->snap(snapping_client); +} + +void game_world::reset() +{ + // reset all entities + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->reset(); + remove_entities(); + + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->post_reset(); + remove_entities(); + + reset_requested = false; +} + +void game_world::remove_entities() +{ + // 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; + } +} + +void game_world::tick() +{ + if(reset_requested) + reset(); + + if(!paused) + { + // update all objects + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->tick(); + + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->tick_defered(); + } + + remove_entities(); +} + +////////////////////////////////////////////////// +// projectile +////////////////////////////////////////////////// +projectile::projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner, + int damage, int flags, float force, int sound_impact, int weapon) +: entity(OBJTYPE_PROJECTILE) +{ + this->type = type; + this->pos = pos; + this->vel = vel * SERVER_TICK_SPEED; // TODO: remove this + this->lifespan = span; + this->owner = owner; + this->powner = powner; + this->flags = flags; + this->force = force; + this->damage = damage; + this->sound_impact = sound_impact; + this->weapon = weapon; + this->bounce = 0; + this->start_tick = server_tick(); + world->insert_entity(this); +} + +void projectile::reset() +{ + world->destroy_entity(this); +} + +void projectile::tick() +{ + float gravity = -400; + if(type != WEAPON_ROCKET) + gravity = -100; + + float pt = (server_tick()-start_tick-1)/(float)SERVER_TICK_SPEED; + float ct = (server_tick()-start_tick)/(float)SERVER_TICK_SPEED; + vec2 prevpos = calc_pos(pos, vel, gravity, pt); + vec2 curpos = calc_pos(pos, vel, gravity, ct); + + lifespan--; + + int collide = col_check_point((int)curpos.x, (int)curpos.y); + + vec2 new_pos; + entity *targetplayer = (entity*)intersect_player(prevpos, curpos, new_pos, powner); + + if(targetplayer || collide || lifespan < 0 ) + { + if (lifespan >= 0 || weapon == WEAPON_ROCKET) + create_sound(pos, sound_impact); + + if (flags & PROJECTILE_FLAGS_EXPLODE) + create_explosion(prevpos, owner, weapon, false); + else if (targetplayer) + { + targetplayer->take_damage(normalize(vel) * max(0.001f, force), damage, owner, weapon); + } + + world->destroy_entity(this); + } +} + +void projectile::snap(int snapping_client) +{ + float ct = (server_tick()-start_tick)/(float)SERVER_TICK_SPEED; + vec2 curpos = calc_pos(pos, vel, -7.5f*SERVER_TICK_SPEED, ct); + + if(distance(players[snapping_client].pos, curpos) > 1000.0f) + return; + + 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->start_tick = start_tick; + proj->type = type; +} + +////////////////////////////////////////////////// +// player +////////////////////////////////////////////////// +// TODO: move to separate file +player::player() +: entity(OBJTYPE_PLAYER_CHARACTER) +{ + init(); +} + +void player::init() +{ + proximity_radius = phys_size; + client_id = -1; + team = -1; // -1 == spectator + extrapowerflags = 0; + + latency_accum = 0; + latency_accum_min = 0; + latency_accum_max = 0; + latency_avg = 0; + latency_min = 0; + latency_max = 0; + + reset(); +} + +void player::reset() +{ + pos = vec2(0.0f, 0.0f); + core.vel = vec2(0.0f, 0.0f); + //direction = vec2(0.0f, 1.0f); + score = 0; + dead = true; + clear_flag(entity::FLAG_PHYSICS); + spawning = false; + die_tick = 0; + damage_taken = 0; + state = STATE_UNKNOWN; + + mem_zero(&input, sizeof(input)); + mem_zero(&previnput, sizeof(previnput)); + num_inputs = 0; + + last_action = -1; + + emote_stop = 0; + damage_taken_tick = 0; + attack_tick = 0; + numobjectshit = 0; + ninjaactivationtick = 0; + currentmovetime = 0; + + active_weapon = WEAPON_GUN; + last_weapon = WEAPON_HAMMER; + wanted_weapon = WEAPON_GUN; +} + +void player::destroy() { } + +void player::set_weapon(int w) +{ + last_weapon = active_weapon; + active_weapon = w; + if(active_weapon < 0 || active_weapon >= NUM_WEAPONS) + active_weapon = 0; +} + +void player::respawn() +{ + spawning = true; +} + + +void player::set_team(int new_team) +{ + team = new_team; + die(client_id, -1); + + dbg_msg("game", "cid=%d team=%d", client_id, team); + + if(team == -1) + clear_flag(FLAG_PHYSICS); + else + set_flag(FLAG_PHYSICS); +} + + +bool try_spawntype(int t, vec2 *outpos) +{ + // get spawn point + int start, num; + map_get_type(t, &start, &num); + if(!num) + return false; + + int id = rand()%num; + mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + id, NULL, NULL); + *outpos = vec2((float)sp->x, (float)sp->y); + return true; +} + +void player::try_respawn() +{ + vec2 spawnpos = vec2(100.0f, -60.0f); + + // get spawn point + if(gameobj->gametype == GAMETYPE_CTF) + { + // try first try own team spawn, then normal spawn and then enemy + if(!try_spawntype(MAPRES_SPAWNPOINT_RED+(team&1), &spawnpos)) + { + if(!try_spawntype(MAPRES_SPAWNPOINT, &spawnpos)) + try_spawntype(MAPRES_SPAWNPOINT_RED+((team+1)&1), &spawnpos); + } + } + else + { + if(!try_spawntype(MAPRES_SPAWNPOINT, &spawnpos)) + try_spawntype(MAPRES_SPAWNPOINT_RED+(rand()&1), &spawnpos); + } + + // check if the position is occupado + entity *ents[2] = {0}; + int types[] = {OBJTYPE_PLAYER_CHARACTER}; + int num_ents = world->find_entities(spawnpos, 64, ents, 2, types, 1); + for(int i = 0; i < num_ents; i++) + { + if(ents[i] != this) + return; + } + + spawning = false; + pos = spawnpos; + + core.pos = pos; + core.vel = vec2(0,0); + core.hooked_player = -1; + + health = 10; + armor = 0; + jumped = 0; + dead = false; + set_flag(entity::FLAG_PHYSICS); + state = STATE_PLAYING; + + core.hook_state = HOOK_IDLE; + + mem_zero(&input, sizeof(input)); + + // init weapons + mem_zero(&weapons, sizeof(weapons)); + weapons[WEAPON_HAMMER].got = true; + weapons[WEAPON_HAMMER].ammo = -1; + weapons[WEAPON_GUN].got = true; + weapons[WEAPON_GUN].ammo = data->weapons[WEAPON_GUN].maxammo; + + //weapons[WEAPON_SNIPER].got = true; + //weapons[WEAPON_SNIPER].ammo = data->weapons[WEAPON_SNIPER].maxammo; + active_weapon = WEAPON_GUN; + last_weapon = WEAPON_HAMMER; + wanted_weapon = WEAPON_GUN; + + reload_timer = 0; + + // Create sound and spawn effects + create_sound(pos, SOUND_PLAYER_SPAWN); + create_spawn(pos); + + gameobj->on_player_spawn(this); +} + +bool player::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; +} + +struct input_count +{ + int presses; + int releases; +}; + +static input_count count_input(int prev, int cur) +{ + input_count c = {0,0}; + prev &= INPUT_STATE_MASK; + cur &= INPUT_STATE_MASK; + int i = prev; + while(i != cur) + { + i = (i+1)&INPUT_STATE_MASK; + if(i&1) + c.presses++; + else + c.releases++; + } + + return c; +} + + +int player::handle_ninja() +{ + vec2 direction = normalize(vec2(input.target_x, input.target_y)); + + if ((server_tick() - ninjaactivationtick) > (data->weapons[WEAPON_NINJA].duration * server_tickspeed() / 1000)) + { + // time's up, return + weapons[WEAPON_NINJA].got = false; + active_weapon = last_weapon; + set_weapon(active_weapon); + return 0; + } + + // Check if it should activate + if (count_input(previnput.fire, input.fire).presses && (server_tick() > currentcooldown)) + { + // ok then, activate ninja + attack_tick = server_tick(); + activationdir = direction; + currentmovetime = data->weapons[WEAPON_NINJA].movetime * server_tickspeed() / 1000; + currentcooldown = data->weapons[WEAPON_NINJA].firedelay * server_tickspeed() / 1000 + server_tick(); + + // reset hit objects + numobjectshit = 0; + + create_sound(pos, SOUND_NINJA_FIRE); + + // release all hooks when ninja is activated + //release_hooked(); + //release_hooks(); + } + + currentmovetime--; + + if (currentmovetime == 0) + { + // reset player velocity + core.vel *= 0.2f; + //return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; + } + + if (currentmovetime > 0) + { + // Set player velocity + core.vel = activationdir * data->weapons[WEAPON_NINJA].velocity; + vec2 oldpos = pos; + move_box(&core.pos, &core.vel, vec2(phys_size, phys_size), 0.0f); + // reset velocity so the client doesn't predict stuff + core.vel = vec2(0.0f,0.0f); + if ((currentmovetime % 2) == 0) + { + create_smoke(pos); + } + + // check if we hit anything along the way + { + int type = OBJTYPE_PLAYER_CHARACTER; + entity *ents[64]; + vec2 dir = pos - oldpos; + float radius = phys_size * 2.0f; //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] == this) + 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, pos) > (phys_size * 2.0f)) + continue; + + // hit a player, give him damage and stuffs... + create_sound(ents[i]->pos, SOUND_NINJA_HIT); + // set his velocity to fast upward (for now) + if(numobjectshit < 10) + hitobjects[numobjectshit++] = ents[i]; + ents[i]->take_damage(vec2(0,10.0f), data->weapons[WEAPON_NINJA].meleedamage, client_id,WEAPON_NINJA); + } + } + return MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION | MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY; + } + + return 0; +} + +int player::handle_sniper() +{ + return 0; + + struct input_count button = count_input(previnput.fire, input.fire); + if (button.releases) + { + vec2 direction = normalize(vec2(input.target_x, input.target_y)); + // Check if we were charging, if so fire + if (weapons[WEAPON_SNIPER].weaponstage >= WEAPONSTAGE_SNIPER_CHARGING) + { + new projectile(projectile::WEAPON_PROJECTILETYPE_SNIPER, + client_id, pos+vec2(0,0), direction*50.0f, + 100 + weapons[WEAPON_SNIPER].weaponstage * 20,this, weapons[WEAPON_SNIPER].weaponstage, 0, 0, -1, WEAPON_SNIPER); + create_sound(pos, SOUND_SNIPER_FIRE); + } + // Add blowback + core.vel = -direction * 10.0f * weapons[WEAPON_SNIPER].weaponstage; + + // update ammo and stuff + weapons[WEAPON_SNIPER].ammo = max(0,weapons[WEAPON_SNIPER].ammo - weapons[WEAPON_SNIPER].weaponstage); + weapons[WEAPON_SNIPER].weaponstage = WEAPONSTAGE_SNIPER_NEUTRAL; + weapons[WEAPON_SNIPER].chargetick = 0; + } + else if (input.fire & 1) + { + // Charge!! (if we are on the ground) + if (is_grounded() && weapons[WEAPON_SNIPER].ammo > 0) + { + if (!weapons[WEAPON_SNIPER].chargetick) + { + weapons[WEAPON_SNIPER].chargetick = server_tick(); + dbg_msg("game", "Chargetick='%d:'", server_tick()); + } + if ((server_tick() - weapons[WEAPON_SNIPER].chargetick) > server_tickspeed() * data->weapons[active_weapon].chargetime) + { + if (weapons[WEAPON_SNIPER].ammo > weapons[WEAPON_SNIPER].weaponstage) + { + weapons[WEAPON_SNIPER].weaponstage++; + weapons[WEAPON_SNIPER].chargetick = server_tick(); + } + else if ((server_tick() - weapons[WEAPON_SNIPER].chargetick) > server_tickspeed() * data->weapons[active_weapon].overchargetime) + { + // Ooopsie, weapon exploded + create_explosion(pos, client_id, WEAPON_SNIPER, false); + create_sound(pos, SOUND_ROCKET_EXPLODE); + // remove this weapon and change weapon to gun + weapons[WEAPON_SNIPER].got = false; + weapons[WEAPON_SNIPER].ammo = 0; + last_weapon = active_weapon; + active_weapon = WEAPON_GUN; + return 0; + } + } + + // While charging, don't move + return MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY|MODIFIER_RETURNFLAGS_NOHOOK; + } + else if (weapons[WEAPON_SNIPER].weaponstage) + weapons[WEAPON_SNIPER].weaponstage = WEAPONSTAGE_SNIPER_NEUTRAL; + } + return 0; +} + +int player::handle_weapons() +{ + vec2 direction = normalize(vec2(input.target_x, input.target_y)); + + if(config.dbg_stress) + { + for(int i = 0; i < NUM_WEAPONS; i++) + { + weapons[i].got = true; + weapons[i].ammo = 10; + } + + if(reload_timer) // twice as fast reload + reload_timer--; + } + + // check reload timer + if(reload_timer) + { + reload_timer--; + return 0; + } + + if (active_weapon == WEAPON_NINJA) + { + // don't update other weapons while ninja is active + return handle_ninja(); + } + + // select weapon + int next = count_input(previnput.next_weapon, input.next_weapon).presses; + int prev = count_input(previnput.prev_weapon, input.prev_weapon).presses; + + if(next < 128) // make sure we only try sane stuff + { + while(next) // next weapon selection + { + wanted_weapon = (wanted_weapon+1)%NUM_WEAPONS; + if(weapons[wanted_weapon].got) + next--; + } + } + + if(prev < 128) // make sure we only try sane stuff + { + while(prev) // prev weapon selection + { + wanted_weapon = (wanted_weapon-1)<0?NUM_WEAPONS-1:wanted_weapon-1; + if(weapons[wanted_weapon].got) + prev--; + } + } + + if(input.wanted_weapon) // direct weapon selection + wanted_weapon = input.wanted_weapon-1; + + if(wanted_weapon < 0 || wanted_weapon >= NUM_WEAPONS) + wanted_weapon = 0; + + // switch weapon if wanted + if(data->weapons[active_weapon].duration <= 0) + { + if(wanted_weapon != -1 && wanted_weapon != active_weapon && wanted_weapon >= 0 && wanted_weapon < NUM_WEAPONS && weapons[wanted_weapon].got) + { + if(active_weapon != wanted_weapon) + create_sound(pos, SOUND_WEAPON_SWITCH); + + set_weapon(wanted_weapon); + } + } + + if (active_weapon == WEAPON_SNIPER) + { + // don't update other weapons while sniper is active + return handle_sniper(); + } + + if(count_input(previnput.fire, input.fire).presses) //previnput.fire != input.fire && (input.fire&1)) + { + if(reload_timer == 0) + { + // fire! + if(weapons[active_weapon].ammo) + { + switch(active_weapon) + { + case WEAPON_HAMMER: + // reset objects hit + numobjectshit = 0; + create_sound(pos, SOUND_HAMMER_FIRE); + break; + + case WEAPON_GUN: + new projectile(WEAPON_GUN, + client_id, + pos+vec2(0,0), + direction*30.0f, + 100, + this, + 1, 0, 0, -1, WEAPON_GUN); + create_sound(pos, SOUND_GUN_FIRE); + break; + case WEAPON_ROCKET: + { + new projectile(WEAPON_ROCKET, + client_id, + pos+vec2(0,0), + direction*15.0f, + 100, + this, + 1, projectile::PROJECTILE_FLAGS_EXPLODE, 0, SOUND_ROCKET_EXPLODE, WEAPON_ROCKET); + create_sound(pos, SOUND_ROCKET_FIRE); + break; + } + case WEAPON_SHOTGUN: + { + int shotspread = 2; + for(int i = -shotspread; i <= shotspread; i++) + { + float spreading[] = {-0.12f, -0.05f, 0, 0.05f, 0.12f}; + float a = get_angle(direction); + float v = 1.0f-fabs(i/(float)shotspread); + a += spreading[i+2]; + new projectile(WEAPON_SHOTGUN, + client_id, + pos+vec2(0,0), + vec2(cosf(a), sinf(a))*(30.0f + 15.0f*v), + //vec2(cosf(a), sinf(a))*20.0f, + (int)(server_tickspeed()*0.3f), + this, + 1, 0, 0, -1, WEAPON_SHOTGUN); + } + create_sound(pos, SOUND_SHOTGUN_FIRE); + break; + } + } + + weapons[active_weapon].ammo--; + attack_tick = server_tick(); + reload_timer = data->weapons[active_weapon].firedelay * server_tickspeed() / 1000; + } + else + { + create_sound(pos, SOUND_WEAPON_NOAMMO); + } + } + } + + // Update weapons + if (active_weapon == WEAPON_HAMMER && reload_timer > 0) + { + // Handle collisions + // 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_CHARACTER; + entity *ents[64]; + vec2 lookdir(direction.x > 0.0f ? 1.0f : -1.0f, 0.0f); + vec2 dir = lookdir * data->weapons[active_weapon].meleereach; + float radius = length(dir * 0.5f); + vec2 center = 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] == this) + 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, pos) > (phys_size * 2.0f)) + continue; + + // hit a player, give him damage and stuffs... + // create sound for bash + //create_sound(ents[i]->pos, sound_impact); + vec2 fdir = normalize(ents[i]->pos- pos); + + // set his velocity to fast upward (for now) + create_smoke(ents[i]->pos); + create_sound(pos, SOUND_HAMMER_HIT); + if(numobjectshit < 10) + hitobjects[numobjectshit++] = ents[i]; + ents[i]->take_damage(vec2(0,-1.0f), data->weapons[active_weapon].meleedamage, client_id, active_weapon); + player* target = (player*)ents[i]; + vec2 dir; + if (length(target->pos - pos) > 0.0f) + dir = normalize(target->pos - pos); + else + dir = vec2(0,-1); + + target->core.vel += normalize(dir + vec2(0,-1.1f)) * 10.0f; + } + } + + if (data->weapons[active_weapon].ammoregentime) + { + // If equipped and not active, regen ammo? + if (reload_timer <= 0) + { + if (weapons[active_weapon].ammoregenstart < 0) + weapons[active_weapon].ammoregenstart = server_tick(); + + if ((server_tick() - weapons[active_weapon].ammoregenstart) >= data->weapons[active_weapon].ammoregentime * server_tickspeed() / 1000) + { + // Add some ammo + weapons[active_weapon].ammo = min(weapons[active_weapon].ammo + 1, data->weapons[active_weapon].maxammo); + weapons[active_weapon].ammoregenstart = -1; + } + } + else + { + weapons[active_weapon].ammoregenstart = -1; + } + } + + return 0; +} + +void player::tick() +{ + // check if we have enough input + // this is to prevent initial weird clicks + if(num_inputs < 2) + previnput = input; + + // do latency stuff + { + CLIENT_INFO info; + if(server_getclientinfo(client_id, &info)) + { + latency_accum += info.latency; + latency_accum_max = max(latency_accum_max, info.latency); + latency_accum_min = min(latency_accum_min, info.latency); + } + + if(server_tick()%server_tickspeed() == 0) + { + latency_avg = latency_accum/server_tickspeed(); + latency_max = latency_accum_max; + latency_min = latency_accum_min; + latency_accum = 0; + latency_accum_min = 1000; + latency_accum_max = 0; + } + } + + // enable / disable physics + if(team == -1 || dead) + world->core.players[client_id] = 0; + else + world->core.players[client_id] = &core; + + // spectator + if(team == -1) + return; + + if(spawning) + try_respawn(); + + // TODO: rework the input to be more robust + if(dead) + { + if(server_tick()-die_tick >= server_tickspeed()*5) // auto respawn after 3 sec + respawn(); + if((input.fire&1) && server_tick()-die_tick >= server_tickspeed()/2) // auto respawn after 0.5 sec + respawn(); + return; + } + + //player_core core; + //core.pos = pos; + //core.jumped = jumped; + core.input = input; + core.tick(); + + // handle weapons + handle_weapons(); + + state = input.state; + + // Previnput + previnput = input; + return; +} + +void player::tick_defered() +{ + if(!dead) + { + vec2 start_pos = core.pos; + vec2 start_vel = core.vel; + bool stuck_before = test_box(core.pos, vec2(28.0f, 28.0f)); + core.move(); + bool stuck_after_move = test_box(core.pos, vec2(28.0f, 28.0f)); + core.quantize(); + bool stuck_after_quant = test_box(core.pos, vec2(28.0f, 28.0f)); + pos = core.pos; + + if(!stuck_before && (stuck_after_move || stuck_after_quant)) + { + dbg_msg("player", "STUCK!!! %d %d %d %f %f %f %f %x %x %x %x", + stuck_before, + stuck_after_move, + stuck_after_quant, + start_pos.x, start_pos.y, + start_vel.x, start_vel.y, + *((unsigned *)&start_pos.x), *((unsigned *)&start_pos.y), + *((unsigned *)&start_vel.x), *((unsigned *)&start_vel.y)); + } + + + int events = core.triggered_events; + int mask = cmask_all_except_one(client_id); + + if(events&COREEVENT_GROUND_JUMP) create_sound(pos, SOUND_PLAYER_JUMP, mask); + if(events&COREEVENT_AIR_JUMP) + { + create_sound(pos, SOUND_PLAYER_JUMP, mask); + ev_common *c = (ev_common *)::events.create(EVENT_AIR_JUMP, sizeof(ev_common), mask); + if(c) + { + c->x = (int)pos.x; + c->y = (int)pos.y; + } + } + + //if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos); + if(events&COREEVENT_HOOK_ATTACH_PLAYER) create_sound(pos, SOUND_HOOK_ATTACH_PLAYER, mask); + if(events&COREEVENT_HOOK_ATTACH_GROUND) create_sound(pos, SOUND_HOOK_ATTACH_GROUND, mask); + //if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos); + + } + + if(team == -1) + { + pos.x = input.target_x; + pos.y = input.target_y; + } +} + +void player::die(int killer, int weapon) +{ + int mode_special = gameobj->on_player_death(this, get_player(killer), weapon); + + dbg_msg("game", "kill killer='%d:%s' victim='%d:%s' weapon=%d special=%d", + killer, server_clientname(killer), + client_id, server_clientname(client_id), weapon, mode_special); + + // send the kill message + msg_pack_start(MSG_KILLMSG, MSGFLAG_VITAL); + msg_pack_int(killer); + msg_pack_int(client_id); + msg_pack_int(weapon); + msg_pack_int(mode_special); + msg_pack_end(); + server_send_msg(-1); + + // a nice sound + create_sound(pos, SOUND_PLAYER_DIE); + + // set dead state + dead = true; + die_tick = server_tick(); + clear_flag(FLAG_PHYSICS); + create_death(pos); +} + +bool player::take_damage(vec2 force, int dmg, int from, int weapon) +{ + core.vel += force; + + if(gameobj->is_friendly_fire(client_id, from) && !config.sv_teamdamage) + return false; + + // player only inflicts half damage on self + if(from == client_id) + dmg = max(1, dmg/2); + + // CTF and TDM (TODO: check for FF) + //if (gameobj->gametype != GAMETYPE_DM && from >= 0 && players[from].team == team) + //return false; + + damage_taken++; + + // create healthmod indicator + if(server_tick() < damage_taken_tick+25) + { + // make sure that the damage indicators doesn't group together + create_damageind(pos, damage_taken*0.25f, dmg); + } + else + { + damage_taken = 0; + create_damageind(pos, 0, dmg); + } + + if(armor) + { + armor -= 1; + dmg--; + } + + if(dmg > armor) + { + dmg -= armor; + armor = 0; + health -= dmg; + } + else + armor -= dmg; + + damage_taken_tick = server_tick(); + + // do damage hit sound + if(from >= 0) + create_sound(get_player(from)->pos, SOUND_HIT, cmask_one(from)); + + // check for death + if(health <= 0) + { + die(from, weapon); + + // set attacker's face to happy (taunt!) + if (from >= 0 && from != client_id) + { + player *p = get_player(from); + + p->emote_type = EMOTE_HAPPY; + p->emote_stop = server_tick() + server_tickspeed(); + } + + return false; + } + + if (dmg > 2) + create_sound(pos, SOUND_PLAYER_PAIN_LONG); + else + create_sound(pos, SOUND_PLAYER_PAIN_SHORT); + + emote_type = EMOTE_PAIN; + emote_stop = server_tick() + 500 * server_tickspeed() / 1000; + + // spawn blood? + return true; +} + +void player::snap(int snaping_client) +{ + if(1) + { + obj_player_info *info = (obj_player_info *)snap_new_item(OBJTYPE_PLAYER_INFO, client_id, sizeof(obj_player_info)); + + info->latency = latency_avg; + info->latency_flux = latency_max-latency_min; + info->local = 0; + info->clientid = client_id; + info->score = score; + info->team = team; + + if(client_id == snaping_client) + info->local = 1; + } + + if(health > 0 && distance(players[snaping_client].pos, pos) < 1000.0f) + { + obj_player_character *character = (obj_player_character *)snap_new_item(OBJTYPE_PLAYER_CHARACTER, client_id, sizeof(obj_player_character)); + + core.write(character); + + // this is to make sure that players that are just standing still + // isn't sent. this is because the physics keep bouncing between + // 0-128 when just standing. + // TODO: fix the physics so this isn't needed + if(snaping_client != client_id && abs(character->vy) < 256.0f) + character->vy = 0; + + if (emote_stop < server_tick()) + { + emote_type = EMOTE_NORMAL; + emote_stop = -1; + } + + character->emote = emote_type; + + character->ammocount = weapons[active_weapon].ammo; + character->health = 0; + character->armor = 0; + character->weapon = active_weapon; + character->weaponstage = weapons[active_weapon].weaponstage; + character->attacktick = attack_tick; + + if(client_id == snaping_client) + { + character->health = health; + character->armor = armor; + } + + if(dead) + character->health = -1; + + //if(length(vel) > 15.0f) + // player->emote = EMOTE_HAPPY; + + //if(damage_taken_tick+50 > server_tick()) + // player->emote = EMOTE_PAIN; + + if (character->emote == EMOTE_NORMAL) + { + if(250 - ((server_tick() - last_action)%(250)) < 5) + character->emote = EMOTE_BLINK; + } + + character->state = state; + } +} + +player *players; + +////////////////////////////////////////////////// +// powerup +////////////////////////////////////////////////// +powerup::powerup(int _type, int _subtype) +: entity(OBJTYPE_POWERUP) +{ + type = _type; + subtype = _subtype; + proximity_radius = phys_size; + + reset(); + + // TODO: should this be done here? + world->insert_entity(this); +} + +void powerup::reset() +{ + if (data->powerupinfo[type].startspawntime > 0) + spawntick = server_tick() + server_tickspeed() * data->powerupinfo[type].startspawntime; + else + spawntick = -1; +} + + +void send_weapon_pickup(int cid, int weapon); + +void powerup::tick() +{ + // wait for respawn + if(spawntick > 0) + { + if(server_tick() > spawntick) + { + // respawn + spawntick = -1; + + if(type == POWERUP_WEAPON) + create_sound(pos, SOUND_WEAPON_SPAWN); + } + else + return; + } + // 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 + int respawntime = -1; + switch (type) + { + case POWERUP_HEALTH: + if(pplayer->health < 10) + { + create_sound(pos, SOUND_PICKUP_HEALTH); + pplayer->health = min(10, pplayer->health + data->powerupinfo[type].amount); + respawntime = data->powerupinfo[type].respawntime; + } + break; + case POWERUP_ARMOR: + if(pplayer->armor < 10) + { + create_sound(pos, SOUND_PICKUP_ARMOR); + pplayer->armor = min(10, pplayer->armor + data->powerupinfo[type].amount); + respawntime = data->powerupinfo[type].respawntime; + } + break; + + case POWERUP_WEAPON: + if(subtype >= 0 && subtype < NUM_WEAPONS) + { + if(pplayer->weapons[subtype].ammo < 10 || !pplayer->weapons[subtype].got) + { + pplayer->weapons[subtype].got = true; + pplayer->weapons[subtype].ammo = min(10, pplayer->weapons[subtype].ammo + data->powerupinfo[type].amount); + respawntime = data->powerupinfo[type].respawntime; + + // TODO: data compiler should take care of stuff like this + if(subtype == WEAPON_ROCKET) + create_sound(pos, SOUND_PICKUP_ROCKET); + else if(subtype == WEAPON_SHOTGUN) + create_sound(pos, SOUND_PICKUP_SHOTGUN); + + send_weapon_pickup(pplayer->client_id, subtype); + } + } + break; + case POWERUP_NINJA: + { + // activate ninja on target player + pplayer->ninjaactivationtick = server_tick(); + pplayer->weapons[WEAPON_NINJA].got = true; + pplayer->last_weapon = pplayer->active_weapon; + pplayer->active_weapon = WEAPON_NINJA; + respawntime = data->powerupinfo[type].respawntime; + create_sound(pos, SOUND_PICKUP_NINJA); + + // loop through all players, setting their emotes + entity *ents[64]; + const int types[] = {OBJTYPE_PLAYER_CHARACTER}; + int num = world->find_entities(vec2(0, 0), 1000000, ents, 64, types, 1); + for (int i = 0; i < num; i++) + { + player *p = (player *)ents[i]; + if (p != pplayer) + { + p->emote_type = EMOTE_SURPRISE; + p->emote_stop = server_tick() + server_tickspeed(); + } + } + + pplayer->emote_type = EMOTE_ANGRY; + pplayer->emote_stop = server_tick() + 1200 * server_tickspeed() / 1000; + + break; + } + default: + break; + }; + + if(respawntime >= 0) + { + dbg_msg("game", "pickup player='%d:%s' item=%d/%d", + pplayer->client_id, server_clientname(pplayer->client_id), type, subtype); + spawntick = server_tick() + server_tickspeed() * respawntime; + } + } +} + +void powerup::snap(int snapping_client) +{ + if(spawntick != -1) + return; + + obj_powerup *up = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup)); + up->x = (int)pos.x; + up->y = (int)pos.y; + up->type = type; // TODO: two diffrent types? what gives? + up->subtype = subtype; +} + +// POWERUP END /////////////////////// + +player *get_player(int index) +{ + return &players[index]; +} + +void create_damageind(vec2 p, float angle, int amount) +{ + float a = 3 * 3.14159f / 2 + angle; + //float a = get_angle(dir); + float s = a-pi/3; + float e = a+pi/3; + for(int i = 0; i < amount; i++) + { + float f = mix(s, e, float(i+1)/float(amount+2)); + ev_damageind *ev = (ev_damageind *)events.create(EVENT_DAMAGEINDICATION, sizeof(ev_damageind)); + if(ev) + { + ev->x = (int)p.x; + ev->y = (int)p.y; + ev->angle = (int)(f*256.0f); + } + } +} + +void create_explosion(vec2 p, int owner, int weapon, bool bnodamage) +{ + // create the event + ev_explosion *ev = (ev_explosion *)events.create(EVENT_EXPLOSION, sizeof(ev_explosion)); + if(ev) + { + ev->x = (int)p.x; + ev->y = (int)p.y; + } + + if (!bnodamage) + { + // deal damage + entity *ents[64]; + const float radius = 128.0f; + const float innerradius = 42.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); + float l = length(diff); + if(l) + forcedir = normalize(diff); + l = 1-clamp((l-innerradius)/(radius-innerradius), 0.0f, 1.0f); + float dmg = 6 * l; + if((int)dmg) + ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner, weapon); + } + } +} + +void create_smoke(vec2 p) +{ + // create the event + ev_explosion *ev = (ev_explosion *)events.create(EVENT_SMOKE, sizeof(ev_explosion)); + if(ev) + { + ev->x = (int)p.x; + ev->y = (int)p.y; + } +} + +void create_spawn(vec2 p) +{ + // create the event + ev_spawn *ev = (ev_spawn *)events.create(EVENT_SPAWN, sizeof(ev_spawn)); + if(ev) + { + ev->x = (int)p.x; + ev->y = (int)p.y; + } +} + +void create_death(vec2 p) +{ + // create the event + ev_death *ev = (ev_death *)events.create(EVENT_DEATH, sizeof(ev_death)); + if(ev) + { + ev->x = (int)p.x; + ev->y = (int)p.y; + } +} + +void create_sound(vec2 pos, int sound, int mask) +{ + if (sound < 0) + return; + + // create a sound + ev_sound *ev = (ev_sound *)events.create(EVENT_SOUND_WORLD, sizeof(ev_sound), mask); + if(ev) + { + ev->x = (int)pos.x; + ev->y = (int)pos.y; + ev->sound = sound; + } +} + +void create_sound_global(int sound, int target) +{ + if (sound < 0) + return; + + msg_pack_start(MSG_SOUND_GLOBAL, MSGFLAG_VITAL); + msg_pack_int(sound); + server_send_msg(-1); +} + +// TODO: should be more general +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; + const int types[] = {OBJTYPE_PLAYER_CHARACTER}; + int num = world->find_entities(center, radius, ents, 64, types, 1); + for (int i = 0; i < num; i++) + { + // Check if entity is a player + if (ents[i] != notthis) + { + new_pos = ents[i]->pos; + return (player*)ents[i]; + } + } + + return 0; +} + + +void send_chat(int cid, int team, const char *msg) +{ + if(cid >= 0 && cid < MAX_CLIENTS) + dbg_msg("chat", "%d:%d:%s: %s", cid, team, server_clientname(cid), msg); + else + dbg_msg("chat", "*** %s", msg); + + if(team == -1) + { + msg_pack_start(MSG_CHAT, MSGFLAG_VITAL); + msg_pack_int(cid); + msg_pack_int(0); + msg_pack_string(msg, 512); + msg_pack_end(); + server_send_msg(-1); + } + else + { + msg_pack_start(MSG_CHAT, MSGFLAG_VITAL); + msg_pack_int(cid); + msg_pack_int(1); + msg_pack_string(msg, 512); + msg_pack_end(); + + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1 && players[i].team == team) + server_send_msg(i); + } + } +} + + +// Server hooks +void mods_tick() +{ + // clear all events + events.clear(); + world->tick(); + + if(world->paused) // make sure that the game object always updates + gameobj->tick(); + + if(config.sv_restart) + { + if(config.sv_restart > 1) + gameobj->do_warmup(config.sv_restart); + else + gameobj->startround(); + + config.sv_restart = 0; + } + + if(config.sv_msg[0] != 0) + { + send_chat(-1, 0, config.sv_msg); + config.sv_msg[0] = 0; + } +} + +void mods_snap(int client_id) +{ + world->snap(client_id); + events.snap(client_id); +} + +void mods_client_input(int client_id, void *input) +{ + if(!world->paused) + { + if (memcmp(&players[client_id].input, input, sizeof(player_input)) != 0) + players[client_id].last_action = server_tick(); + + //players[client_id].previnput = players[client_id].input; + players[client_id].input = *(player_input*)input; + players[client_id].num_inputs++; + } +} + +void send_info(int who, int to_who) +{ + msg_pack_start(MSG_SETINFO, MSGFLAG_VITAL); + msg_pack_int(who); + msg_pack_string(server_clientname(who), 64); + msg_pack_string(players[who].skin_name, 64); + msg_pack_int(players[who].use_custom_color); + msg_pack_int(players[who].color_body); + msg_pack_int(players[who].color_feet); + msg_pack_end(); + server_send_msg(to_who); +} + +void send_emoticon(int cid, int emoticon) +{ + msg_pack_start(MSG_EMOTICON, MSGFLAG_VITAL); + msg_pack_int(cid); + msg_pack_int(emoticon % 16); + msg_pack_end(); + server_send_msg(-1); +} + +void send_weapon_pickup(int cid, int weapon) +{ + msg_pack_start(MSG_WEAPON_PICKUP, MSGFLAG_VITAL); + msg_pack_int(weapon); + msg_pack_end(); + server_send_msg(cid); +} + +void mods_client_enter(int client_id) +{ + world->insert_entity(&players[client_id]); + players[client_id].respawn(); + dbg_msg("game", "join player='%d:%s'", client_id, server_clientname(client_id)); + + char buf[512]; + sprintf(buf, "%s has joined the game", server_clientname(client_id)); + send_chat(-1, -1, buf); +} + +void mods_connected(int client_id) +{ + players[client_id].init(); + players[client_id].client_id = client_id; + + //dbg_msg("game", "connected player='%d:%s'", client_id, server_clientname(client_id)); + + // Check which team the player should be on + if(gameobj->gametype == GAMETYPE_DM) + players[client_id].team = 0; + else + players[client_id].team = gameobj->getteam(client_id); +} + +void mods_client_drop(int client_id) +{ + char buf[512]; + sprintf(buf, "%s has left the game", server_clientname(client_id)); + send_chat(-1, -1, buf); + + dbg_msg("game", "leave player='%d:%s'", client_id, server_clientname(client_id)); + + gameobj->on_player_death(&players[client_id], 0, -1); + world->remove_entity(&players[client_id]); + world->core.players[client_id] = 0x0; + players[client_id].client_id = -1; +} + +void mods_message(int msg, int client_id) +{ + if(msg == MSG_SAY) + { + int team = msg_unpack_int(); + const char *text = msg_unpack_string(); + if(team) + team = players[client_id].team; + else + team = -1; + send_chat(client_id, team, text); + } + else if (msg == MSG_SETTEAM) + { + // Switch team on given client and kill/respawn him + players[client_id].set_team(msg_unpack_int()); + gameobj->on_player_info_change(&players[client_id]); + + // send all info to this client + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1) + send_info(i, -1); + } + } + else if (msg == MSG_CHANGEINFO || msg == MSG_STARTINFO) + { + const char *name = msg_unpack_string(); + const char *skin_name = msg_unpack_string(); + players[client_id].use_custom_color = msg_unpack_int(); + players[client_id].color_body = msg_unpack_int(); + players[client_id].color_feet = msg_unpack_int(); + + // check for invalid chars + const char *p = name; + while (*p) + { + if(*p < 32) + return; + p++; + } + + // + if(msg == MSG_CHANGEINFO && strcmp(name, server_clientname(client_id)) != 0) + { + char msg[256]; + sprintf(msg, "*** %s changed name to %s", server_clientname(client_id), name); + send_chat(-1, -1, msg); + } + + //send_set_name(client_id, players[client_id].name, name); + strncpy(players[client_id].skin_name, skin_name, 64); + server_setclientname(client_id, name); + + gameobj->on_player_info_change(&players[client_id]); + + if(msg == MSG_STARTINFO) + { + // send all info to this client + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(players[i].client_id != -1) + send_info(i, client_id); + } + + msg_pack_start(MSG_READY_TO_ENTER, MSGFLAG_VITAL); + msg_pack_end(); + server_send_msg(client_id); + } + + send_info(client_id, -1); + } + else if (msg == MSG_EMOTICON) + { + int emoteicon = msg_unpack_int(); + send_emoticon(client_id, emoteicon % 16); + } +} + +extern unsigned char internal_data[]; + +void mods_init() +{ + if(!data) /* only load once */ + data = load_data_from_memory(internal_data); + + col_init(32); + + world = new game_world; + players = new player[MAX_CLIENTS]; + + // select gametype + if(strcmp(config.sv_gametype, "ctf") == 0) + gameobj = new gameobject_ctf; + else if(strcmp(config.sv_gametype, "tdm") == 0) + gameobj = new gameobject_tdm; + else + gameobj = new gameobject_dm; + + // setup core world + for(int i = 0; i < MAX_CLIENTS; i++) + players[i].core.world = &world->core; + + // + int start, num; + map_get_type(MAPRES_ITEM, &start, &num); + + // TODO: this is way more complicated then it should be + for(int i = 0; i < num; i++) + { + mapres_item *it = (mapres_item *)map_get_item(start+i, 0, 0); + + int type = -1; + int subtype = 0; + + switch(it->type) + { + case ITEM_WEAPON_GUN: + type = POWERUP_WEAPON; + subtype = WEAPON_GUN; + break; + case ITEM_WEAPON_SHOTGUN: + type = POWERUP_WEAPON; + subtype = WEAPON_SHOTGUN; + break; + case ITEM_WEAPON_ROCKET: + type = POWERUP_WEAPON; + subtype = WEAPON_ROCKET; + break; + case ITEM_WEAPON_HAMMER: + type = POWERUP_WEAPON; + subtype = WEAPON_HAMMER; + break; + + case ITEM_HEALTH: + type = POWERUP_HEALTH; + break; + + case ITEM_ARMOR: + type = POWERUP_ARMOR; + break; + + case ITEM_NINJA: + if(config.sv_powerups) + { + type = POWERUP_NINJA; + subtype = WEAPON_NINJA; + } + break; + }; + + if(type != -1) + { + // LOL, the only new in the entire game code + // perhaps we can get rid of it. seems like a stupid thing to have + powerup *ppower = new powerup(type, subtype); + ppower->pos = vec2(it->x, it->y); + } + } + + if(gameobj->gametype == GAMETYPE_CTF) + { + } + + world->insert_entity(gameobj); + + + if(config.dbg_bots) + { + + for(int i = 0; i < config.dbg_bots ; i++) + { + mods_connected(MAX_CLIENTS-i-1); + mods_client_enter(MAX_CLIENTS-i-1); + if(gameobj->gametype != GAMETYPE_DM) + players[MAX_CLIENTS-i-1].team = i&1; + } + } +} + +void mods_shutdown() +{ + delete [] players; + delete gameobj; + delete world; + gameobj = 0; + players = 0; + world = 0; +} + +void mods_presnap() {} +void mods_postsnap() {} + +extern "C" const char *mods_net_version() { return TEEWARS_NETVERSION; } diff --git a/src/game/server/srv_common.cpp b/src/game/server/srv_common.cpp deleted file mode 100644 index 83754780..00000000 --- a/src/game/server/srv_common.cpp +++ /dev/null @@ -1,264 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "srv_common.h" -#include - -gameobject::gameobject() -: entity(OBJTYPE_GAME) -{ - // select gametype - if(strcmp(config.sv_gametype, "ctf") == 0) - { - gametype = GAMETYPE_CTF; - dbg_msg("game", "-- Capture The Flag --"); - } - else if(strcmp(config.sv_gametype, "tdm") == 0) - { - gametype = GAMETYPE_TDM; - dbg_msg("game", "-- Team Death Match --"); - } - else - { - gametype = GAMETYPE_DM; - dbg_msg("game", "-- Death Match --"); - } - - // - do_warmup(config.sv_warmup); - game_over_tick = -1; - sudden_death = 0; - round_start_tick = server_tick(); - round_count = 0; - is_teamplay = false; - teamscore[0] = 0; - teamscore[1] = 0; -} - -void gameobject::endround() -{ - if(warmup) // game can't end when we are running warmup - return; - - world->paused = true; - game_over_tick = server_tick(); - sudden_death = 0; -} - -void gameobject::resetgame() -{ - world->reset_requested = true; -} - -static bool is_separator(char c) -{ - return c == ';' || c == ' ' || c == ',' || c == '\t'; -} - -void gameobject::startround() -{ - resetgame(); - - round_start_tick = server_tick(); - sudden_death = 0; - game_over_tick = -1; - world->paused = false; - teamscore[0] = 0; - teamscore[1] = 0; - round_count++; -} - -void gameobject::cyclemap() -{ - if(!strlen(config.sv_maprotation)) - return; - // handle maprotation - char buf[512]; - const char *s = strstr(config.sv_maprotation, config.sv_map); - if(s == 0) - s = config.sv_maprotation; // restart rotation - else - { - s += strlen(config.sv_map); // skip this map - while(is_separator(s[0])) - s++; - if(s[0] == 0) - s = config.sv_maprotation; // restart rotation - } - - int i = 0; - for(; i < 512; i++) - { - buf[i] = s[i]; - if(is_separator(s[i]) || s[i] == 0) - { - buf[i] = 0; - break; - } - } - - i = 0; // skip spaces - while(is_separator(buf[i])) - i++; - - dbg_msg("game", "rotating map to %s", &buf[i]); - strcpy(config.sv_map, &buf[i]); -} - -void gameobject::post_reset() -{ - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1) - players[i].respawn(); - } -} - -void gameobject::on_player_info_change(class player *p) -{ - const int team_colors[2] = {65387, 10223467}; - if(is_teamplay) - { - if(p->team >= 0 || p->team <= 1) - { - p->use_custom_color = 1; - p->color_body = team_colors[p->team]; - p->color_feet = team_colors[p->team]; - } - } -} - - -int gameobject::on_player_death(class player *victim, class player *killer, int weapon) -{ - // do scoreing - if(!killer) - return 0; - if(killer == victim) - victim->score--; // suicide - else - { - if(is_teamplay && victim->team == killer->team) - killer->score--; // teamkill - else - killer->score++; // normal kill - } - return 0; -} - -void gameobject::do_warmup(int seconds) -{ - warmup = seconds*SERVER_TICK_SPEED; -} - -bool gameobject::is_friendly_fire(int cid1, int cid2) -{ - if(cid1 == cid2) - return false; - - if(is_teamplay) - { - if(players[cid1].team == players[cid2].team) - return true; - } - - return false; -} - -void gameobject::tick() -{ - // do warmup - if(warmup) - { - warmup--; - if(!warmup) - startround(); - } - - if(game_over_tick != -1) - { - // game over.. wait for restart - if(server_tick() > game_over_tick+server_tickspeed()*10) - { - cyclemap(); - startround(); - } - } - - - // update browse info - int prog = -1; - if(config.sv_timelimit > 0) - prog = max(prog, (server_tick()-round_start_tick) * 100 / (config.sv_timelimit*server_tickspeed()*60)); - - if(config.sv_scorelimit) - { - if(is_teamplay) - { - prog = max(prog, (teamscore[0]*100)/config.sv_scorelimit); - prog = max(prog, (teamscore[1]*100)/config.sv_scorelimit); - } - else - { - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1) - prog = max(prog, (players[i].score*100)/config.sv_scorelimit); - } - } - } - - if(warmup) - prog = -1; - - server_setbrowseinfo(gametype, prog); -} - -void gameobject::snap(int snapping_client) -{ - obj_game *game = (obj_game *)snap_new_item(OBJTYPE_GAME, 0, sizeof(obj_game)); - game->paused = world->paused; - game->game_over = game_over_tick==-1?0:1; - game->sudden_death = sudden_death; - - game->score_limit = config.sv_scorelimit; - game->time_limit = config.sv_timelimit; - game->round_start_tick = round_start_tick; - game->gametype = gametype; - - game->warmup = warmup; - - game->teamscore[0] = teamscore[0]; - game->teamscore[1] = teamscore[1]; -} - -int gameobject::getteam(int notthisid) -{ - int numplayers[2] = {0,0}; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1 && players[i].client_id != notthisid) - { - numplayers[players[i].team]++; - } - } - - return numplayers[0] > numplayers[1] ? 1 : 0; -} - -void gameobject::do_team_wincheck() -{ - if(game_over_tick == -1 && !warmup) - { - // check score win condition - if((config.sv_scorelimit > 0 && (teamscore[0] >= config.sv_scorelimit || teamscore[1] >= config.sv_scorelimit)) || - (config.sv_timelimit > 0 && (server_tick()-round_start_tick) >= config.sv_timelimit*server_tickspeed()*60)) - { - if(teamscore[0] != teamscore[1]) - endround(); - else - sudden_death = 1; - } - } -} - -gameobject *gameobj = 0; diff --git a/src/game/server/srv_common.h b/src/game/server/srv_common.h deleted file mode 100644 index b28d3066..00000000 --- a/src/game/server/srv_common.h +++ /dev/null @@ -1,327 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include "../game.h" -#include "data.h" - - -void create_sound_global(int sound, int target=-1); - -inline int cmask_all() { return -1; } -inline int cmask_one(int cid) { return 1<stand_pos = vec2(stand->x, stand->y); - f->pos = f->stand_pos; - flags[i] = f; - //dbg_msg("game", "flag at %f,%f", f->pos.x, f->pos.y); - } - else - { - // report massive failure - flags[i] = 0; - } - } - - is_teamplay = true; -} - -void gameobject_ctf::on_player_spawn(class player *p) -{ -} - -int gameobject_ctf::on_player_death(class player *victim, class player *killer, int weaponid) -{ - gameobject::on_player_death(victim, killer, weaponid); - int had_flag = 0; - - // drop flags - for(int fi = 0; fi < 2; fi++) - { - flag *f = flags[fi]; - if(f && f->carrying_player == killer) - had_flag |= 2; - if(f && f->carrying_player == victim) - { - create_sound_global(SOUND_CTF_DROP); - f->drop_tick = server_tick(); - f->carrying_player = 0; - f->vel = vec2(0,0); - - if(killer && killer->team != victim->team) - killer->score++; - - had_flag |= 1; - } - } - - return had_flag; -} - -void gameobject_ctf::tick() -{ - gameobject::tick(); - - do_team_wincheck(); - - // do flags - for(int fi = 0; fi < 2; fi++) - { - flag *f = flags[fi]; - - if(!f) - continue; - - // - if(f->carrying_player) - { - // update flag position - f->pos = f->carrying_player->pos; - - if(flags[fi^1]->at_stand) - { - if(distance(f->pos, flags[fi^1]->pos) < 24) - { - // CAPTURE! \o/ - teamscore[fi^1] += 100; - f->carrying_player->score += 5; - for(int i = 0; i < 2; i++) - flags[i]->reset(); - - dbg_msg("", "capture sound %d", SOUND_CTF_CAPTURE); - create_sound_global(SOUND_CTF_CAPTURE); - } - } - } - else - { - player *players[MAX_CLIENTS]; - int types[] = {OBJTYPE_PLAYER_CHARACTER}; - int num = world->find_entities(f->pos, 32.0f, (entity**)players, MAX_CLIENTS, types, 1); - for(int i = 0; i < num; i++) - { - if(players[i]->team == f->team) - { - // return the flag - if(!f->at_stand) - { - players[i]->score += 1; - create_sound_global(SOUND_CTF_RETURN); - f->reset(); - } - } - else - { - // take the flag - if(f->at_stand) - teamscore[fi^1]++; - f->at_stand = 0; - f->carrying_player = players[i]; - f->carrying_player->score += 1; - create_sound_global(SOUND_CTF_GRAB); - break; - } - } - - if(!f->carrying_player && !f->at_stand) - { - if(server_tick() > f->drop_tick + SERVER_TICK_SPEED*30) - { - create_sound_global(SOUND_CTF_RETURN); - f->reset(); - } - else - { - f->vel.y += gravity; - move_box(&f->pos, &f->vel, vec2(f->phys_size, f->phys_size), 0.5f); - } - } - } - } -} - -// Flag -flag::flag(int _team) -: entity(OBJTYPE_FLAG) -{ - team = _team; - proximity_radius = phys_size; - carrying_player = 0x0; - - reset(); - - // TODO: should this be done here? - world->insert_entity(this); -} - -void flag::reset() -{ - carrying_player = 0; - at_stand = 1; - pos = stand_pos; - vel = vec2(0,0); -} - -void flag::snap(int snapping_client) -{ - obj_flag *flag = (obj_flag *)snap_new_item(OBJTYPE_FLAG, team, sizeof(obj_flag)); - flag->x = (int)pos.x; - flag->y = (int)pos.y; - flag->team = team; - flag->carried_by = -1; - - if(at_stand) - flag->carried_by = -2; - else if(carrying_player) - flag->carried_by = carrying_player->client_id; -} diff --git a/src/game/server/srv_ctf.h b/src/game/server/srv_ctf.h deleted file mode 100644 index 02acef37..00000000 --- a/src/game/server/srv_ctf.h +++ /dev/null @@ -1,33 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -// game object -class gameobject_ctf : public gameobject -{ -public: - class flag *flags[2]; - - gameobject_ctf(); - virtual void tick(); - - virtual void on_player_spawn(class player *p); - virtual int on_player_death(class player *victim, class player *killer, int weapon); -}; - -// TODO: move to seperate file -class flag : public entity -{ -public: - static const int phys_size = 14; - player *carrying_player; - vec2 vel; - vec2 stand_pos; - - int team; - int at_stand; - int drop_tick; - - flag(int _team); - - virtual void reset(); - virtual void snap(int snapping_client); -}; diff --git a/src/game/server/srv_dm.cpp b/src/game/server/srv_dm.cpp deleted file mode 100644 index d5720856..00000000 --- a/src/game/server/srv_dm.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "srv_common.h" -#include "srv_dm.h" - -void gameobject_dm::tick() -{ - if(game_over_tick == -1) - { - // game is running - - // gather some stats - int topscore = 0; - int topscore_count = 0; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(players[i].client_id != -1) - { - if(players[i].score > topscore) - { - topscore = players[i].score; - topscore_count = 1; - } - else if(players[i].score == topscore) - topscore_count++; - } - } - - // check score win condition - if((config.sv_scorelimit > 0 && topscore >= config.sv_scorelimit) || - (config.sv_timelimit > 0 && (server_tick()-round_start_tick) >= config.sv_timelimit*server_tickspeed()*60)) - { - if(topscore_count == 1) - endround(); - else - sudden_death = 1; - } - } - - gameobject::tick(); -} - diff --git a/src/game/server/srv_dm.h b/src/game/server/srv_dm.h deleted file mode 100644 index 96bff3ae..00000000 --- a/src/game/server/srv_dm.h +++ /dev/null @@ -1,7 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -// game object -class gameobject_dm : public gameobject -{ -public: - virtual void tick(); -}; diff --git a/src/game/server/srv_tdm.cpp b/src/game/server/srv_tdm.cpp deleted file mode 100644 index bcae397f..00000000 --- a/src/game/server/srv_tdm.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "srv_common.h" -#include "srv_tdm.h" - -gameobject_tdm::gameobject_tdm() -{ - is_teamplay = true; -} - - -int gameobject_tdm::on_player_death(class player *victim, class player *killer, int weapon) -{ - gameobject::on_player_death(victim, killer, weapon); - - if(weapon >= 0) - { - // do team scoring - if(killer == victim) - teamscore[killer->team&1]--; // klant arschel - else - teamscore[killer->team&1]++; // good shit - } - return 0; -} - -void gameobject_tdm::tick() -{ - do_team_wincheck(); - - gameobject::tick(); -} diff --git a/src/game/server/srv_tdm.h b/src/game/server/srv_tdm.h deleted file mode 100644 index 70b4646e..00000000 --- a/src/game/server/srv_tdm.h +++ /dev/null @@ -1,10 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -// game object -class gameobject_tdm : public gameobject -{ -public: - gameobject_tdm(); - - int on_player_death(class player *victim, class player *killer, int weapon); - virtual void tick(); -}; -- cgit 1.4.1