From a420eb543f8206730aebb80e60a625f7204694e4 Mon Sep 17 00:00:00 2001 From: Magnus Auvinen Date: Thu, 14 Aug 2008 18:25:44 +0000 Subject: moved alot of stuff to their own cpp/hpp files --- src/game/server/entities/character.cpp | 1131 +++++++++++++++++++++++++++++++ src/game/server/entities/character.hpp | 122 ++++ src/game/server/entities/laser.cpp | 112 +++ src/game/server/entities/laser.hpp | 31 + src/game/server/entities/pickup.cpp | 142 ++++ src/game/server/entities/pickup.hpp | 24 + src/game/server/entities/projectile.cpp | 108 +++ src/game/server/entities/projectile.hpp | 38 ++ src/game/server/entity.hpp | 83 +++ src/game/server/eventhandler.hpp | 25 + src/game/server/gamecontext.hpp | 38 ++ src/game/server/gamecontroller.cpp | 418 ++++++++++++ src/game/server/gamecontroller.hpp | 97 +++ src/game/server/gamemodes/ctf.cpp | 200 ++++++ src/game/server/gamemodes/ctf.hpp | 35 + src/game/server/gamemodes/dm.cpp | 8 + src/game/server/gamemodes/dm.hpp | 9 + src/game/server/gamemodes/tdm.cpp | 31 + src/game/server/gamemodes/tdm.hpp | 12 + src/game/server/gameworld.hpp | 126 ++++ src/game/server/gs_common.hpp | 587 +--------------- src/game/server/gs_ent_pickup.cpp | 140 ---- src/game/server/gs_ent_player.cpp | 1128 ------------------------------ src/game/server/gs_game.cpp | 416 ------------ src/game/server/gs_game_ctf.cpp | 198 ------ src/game/server/gs_game_ctf.hpp | 33 - src/game/server/gs_game_dm.cpp | 10 - src/game/server/gs_game_dm.hpp | 7 - src/game/server/gs_game_tdm.cpp | 30 - src/game/server/gs_game_tdm.hpp | 10 - src/game/server/gs_server.cpp | 226 +----- src/game/server/player.hpp | 55 ++ 32 files changed, 2859 insertions(+), 2771 deletions(-) create mode 100644 src/game/server/entities/character.cpp create mode 100644 src/game/server/entities/character.hpp create mode 100644 src/game/server/entities/laser.cpp create mode 100644 src/game/server/entities/laser.hpp create mode 100644 src/game/server/entities/pickup.cpp create mode 100644 src/game/server/entities/pickup.hpp create mode 100644 src/game/server/entities/projectile.cpp create mode 100644 src/game/server/entities/projectile.hpp create mode 100644 src/game/server/entity.hpp create mode 100644 src/game/server/eventhandler.hpp create mode 100644 src/game/server/gamecontext.hpp create mode 100644 src/game/server/gamecontroller.cpp create mode 100644 src/game/server/gamecontroller.hpp create mode 100644 src/game/server/gamemodes/ctf.cpp create mode 100644 src/game/server/gamemodes/ctf.hpp create mode 100644 src/game/server/gamemodes/dm.cpp create mode 100644 src/game/server/gamemodes/dm.hpp create mode 100644 src/game/server/gamemodes/tdm.cpp create mode 100644 src/game/server/gamemodes/tdm.hpp create mode 100644 src/game/server/gameworld.hpp delete mode 100644 src/game/server/gs_ent_pickup.cpp delete mode 100644 src/game/server/gs_ent_player.cpp delete mode 100644 src/game/server/gs_game.cpp delete mode 100644 src/game/server/gs_game_ctf.cpp delete mode 100644 src/game/server/gs_game_ctf.hpp delete mode 100644 src/game/server/gs_game_dm.cpp delete mode 100644 src/game/server/gs_game_dm.hpp delete mode 100644 src/game/server/gs_game_tdm.cpp delete mode 100644 src/game/server/gs_game_tdm.hpp create mode 100644 src/game/server/player.hpp (limited to 'src') diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp new file mode 100644 index 00000000..160e080a --- /dev/null +++ b/src/game/server/entities/character.cpp @@ -0,0 +1,1131 @@ +#include +#include +#include +#include + +#include "character.hpp" +#include "laser.hpp" +#include "projectile.hpp" + +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; +} + +// player +CHARACTER::CHARACTER() +: ENTITY(NETOBJTYPE_CHARACTER) +{} + +void CHARACTER::reset() +{ + +} + +bool CHARACTER::spawn(PLAYER *player, vec2 pos, int team) +{ + player_state = PLAYERSTATE_UNKNOWN; + emote_stop = -1; + last_action = -1; + active_weapon = WEAPON_GUN; + last_weapon = WEAPON_HAMMER; + queued_weapon = -1; + + //clear(); + this->player = player; + this->pos = pos; + this->team = team; + + core.reset(); + core.world = &game.world.core; + core.pos = pos; + game.world.core.characters[player->client_id] = &core; + + game.world.insert_entity(this); + alive = true; + return true; +} + +void CHARACTER::destroy() +{ + game.world.core.characters[player->client_id] = 0; + alive = false; +} + +void CHARACTER::set_weapon(int w) +{ + if(w == active_weapon) + return; + + last_weapon = active_weapon; + queued_weapon = -1; + active_weapon = w; + if(active_weapon < 0 || active_weapon >= NUM_WEAPONS) + active_weapon = 0; + + game.create_sound(pos, SOUND_WEAPON_SWITCH); +} + +bool CHARACTER::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; +} + + +int CHARACTER::handle_ninja() +{ + vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y)); + + if ((server_tick() - ninja.activationtick) > (data->weapons.ninja.duration * server_tickspeed() / 1000)) + { + // time's up, return + weapons[WEAPON_NINJA].got = false; + active_weapon = last_weapon; + if(active_weapon == WEAPON_NINJA) + active_weapon = WEAPON_GUN; + set_weapon(active_weapon); + return 0; + } + + // force ninja weapon + set_weapon(WEAPON_NINJA); + + // Check if it should activate + if (count_input(latest_previnput.fire, latest_input.fire).presses && (server_tick() > ninja.currentcooldown)) + { + // ok then, activate ninja + attack_tick = server_tick(); + ninja.activationdir = direction; + ninja.currentmovetime = data->weapons.ninja.movetime * server_tickspeed() / 1000; + ninja.currentcooldown = data->weapons.ninja.base->firedelay * server_tickspeed() / 1000 + server_tick(); + + // reset hit objects + numobjectshit = 0; + + game.create_sound(pos, SOUND_NINJA_FIRE); + + // release all hooks when ninja is activated + //release_hooked(); + //release_hooks(); + } + + ninja.currentmovetime--; + + if (ninja.currentmovetime == 0) + { + // reset player velocity + core.vel *= 0.2f; + //return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; + } + + if (ninja.currentmovetime > 0) + { + // Set player velocity + core.vel = ninja.activationdir * data->weapons.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 ((ninja.currentmovetime % 2) == 0) + { + //create_smoke(pos); + } + + // check if we hit anything along the way + { + CHARACTER *ents[64]; + vec2 dir = pos - oldpos; + float radius = phys_size * 2.0f; //length(dir * 0.5f); + vec2 center = oldpos + dir * 0.5f; + int num = game.world.find_entities(center, radius, (ENTITY**)ents, 64, NETOBJTYPE_CHARACTER); + + 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... + game.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.ninja.base->damage, player->client_id,WEAPON_NINJA); + } + } + return 0; + } + + return 0; +} + + +void CHARACTER::do_weaponswitch() +{ + if(reload_timer != 0) // make sure we have reloaded + return; + + if(queued_weapon == -1) // check for a queued weapon + return; + + if(weapons[WEAPON_NINJA].got) // if we have ninja, no weapon selection is possible + return; + + // switch weapon + set_weapon(queued_weapon); +} + +void CHARACTER::handle_weaponswitch() +{ + int wanted_weapon = active_weapon; + if(queued_weapon != -1) + wanted_weapon = queued_weapon; + + // select weapon + int next = count_input(latest_previnput.next_weapon, latest_input.next_weapon).presses; + int prev = count_input(latest_previnput.prev_weapon, latest_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--; + } + } + + // direct weapon selection + if(latest_input.wanted_weapon) + wanted_weapon = input.wanted_weapon-1; + + // check for insane values + if(wanted_weapon >= 0 && wanted_weapon < NUM_WEAPONS && wanted_weapon != active_weapon && weapons[wanted_weapon].got) + queued_weapon = wanted_weapon; + + do_weaponswitch(); +} + +void CHARACTER::fire_weapon() +{ + if(reload_timer != 0 || active_weapon == WEAPON_NINJA) + return; + + do_weaponswitch(); + + vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y)); + + bool fullauto = false; + if(active_weapon == WEAPON_GRENADE || active_weapon == WEAPON_SHOTGUN || active_weapon == WEAPON_RIFLE) + fullauto = true; + + + // check if we gonna fire + bool will_fire = false; + if(count_input(latest_previnput.fire, latest_input.fire).presses) will_fire = true; + if(fullauto && (latest_input.fire&1) && weapons[active_weapon].ammo) will_fire = true; + if(!will_fire) + return; + + // check for ammo + if(!weapons[active_weapon].ammo) + { + game.create_sound(pos, SOUND_WEAPON_NOAMMO); + return; + } + + vec2 projectile_startpos = pos+direction*phys_size*0.75f; + + switch(active_weapon) + { + case WEAPON_HAMMER: + { + // reset objects hit + numobjectshit = 0; + game.create_sound(pos, SOUND_HAMMER_FIRE); + + CHARACTER *ents[64]; + int num = game.world.find_entities(pos+direction*phys_size*0.75f, phys_size*0.5f, (ENTITY**)ents, 64, NETOBJTYPE_CHARACTER); + + for (int i = 0; i < num; i++) + { + CHARACTER *target = ents[i]; + if (target == this) + continue; + + // hit a player, give him damage and stuffs... + vec2 fdir = normalize(ents[i]->pos - pos); + + // set his velocity to fast upward (for now) + game.create_sound(pos, SOUND_HAMMER_HIT); + ents[i]->take_damage(vec2(0,-1.0f), data->weapons.hammer.base->damage, player->client_id, active_weapon); + 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; + } + + } break; + + case WEAPON_GUN: + { + PROJECTILE *proj = new PROJECTILE(WEAPON_GUN, + player->client_id, + projectile_startpos, + direction, + (int)(server_tickspeed()*tuning.gun_lifetime), + this, + 1, 0, 0, -1, WEAPON_GUN); + + // pack the projectile and send it to the client directly + NETOBJ_PROJECTILE p; + proj->fill_info(&p); + + msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0); + msg_pack_int(1); + for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++) + msg_pack_int(((int *)&p)[i]); + msg_pack_end(); + server_send_msg(player->client_id); + + game.create_sound(pos, SOUND_GUN_FIRE); + } break; + + case WEAPON_SHOTGUN: + { + int shotspread = 2; + + msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0); + msg_pack_int(shotspread*2+1); + + for(int i = -shotspread; i <= shotspread; i++) + { + float spreading[] = {-0.185f, -0.070f, 0, 0.070f, 0.185f}; + float a = get_angle(direction); + a += spreading[i+2]; + float v = 1-(abs(i)/(float)shotspread); + float speed = mix((float)tuning.shotgun_speeddiff, 1.0f, v); + PROJECTILE *proj = new PROJECTILE(WEAPON_SHOTGUN, + player->client_id, + projectile_startpos, + vec2(cosf(a), sinf(a))*speed, + (int)(server_tickspeed()*tuning.shotgun_lifetime), + this, + 1, 0, 0, -1, WEAPON_SHOTGUN); + + // pack the projectile and send it to the client directly + NETOBJ_PROJECTILE p; + proj->fill_info(&p); + + for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++) + msg_pack_int(((int *)&p)[i]); + } + + msg_pack_end(); + server_send_msg(player->client_id); + + game.create_sound(pos, SOUND_SHOTGUN_FIRE); + } break; + + case WEAPON_GRENADE: + { + PROJECTILE *proj = new PROJECTILE(WEAPON_GRENADE, + player->client_id, + projectile_startpos, + direction, + (int)(server_tickspeed()*tuning.grenade_lifetime), + this, + 1, PROJECTILE::PROJECTILE_FLAGS_EXPLODE, 0, SOUND_GRENADE_EXPLODE, WEAPON_GRENADE); + + // pack the projectile and send it to the client directly + NETOBJ_PROJECTILE p; + proj->fill_info(&p); + + msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0); + msg_pack_int(1); + for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++) + msg_pack_int(((int *)&p)[i]); + msg_pack_end(); + server_send_msg(player->client_id); + + game.create_sound(pos, SOUND_GRENADE_FIRE); + } break; + + case WEAPON_RIFLE: + { + new LASER(pos, direction, tuning.laser_reach, this); + game.create_sound(pos, SOUND_RIFLE_FIRE); + } break; + + } + + if(weapons[active_weapon].ammo > 0) // -1 == unlimited + weapons[active_weapon].ammo--; + attack_tick = server_tick(); + reload_timer = data->weapons.id[active_weapon].firedelay * server_tickspeed() / 1000; +} + +int CHARACTER::handle_weapons() +{ + vec2 direction = normalize(vec2(latest_input.target_x, latest_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(); + } + + // fire weapon, if wanted + fire_weapon(); + + // ammo regen + int ammoregentime = data->weapons.id[active_weapon].ammoregentime; + if(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) >= ammoregentime * server_tickspeed() / 1000) + { + // Add some ammo + weapons[active_weapon].ammo = min(weapons[active_weapon].ammo + 1, 10); + weapons[active_weapon].ammoregenstart = -1; + } + } + else + { + weapons[active_weapon].ammoregenstart = -1; + } + } + + return 0; +} + +void CHARACTER::on_predicted_input(NETOBJ_PLAYER_INPUT *new_input) +{ + // check for changes + if(mem_comp(&input, new_input, sizeof(NETOBJ_PLAYER_INPUT)) != 0) + last_action = server_tick(); + + // copy new input + mem_copy(&input, new_input, sizeof(input)); + num_inputs++; + + // or are not allowed to aim in the center + if(input.target_x == 0 && input.target_y == 0) + input.target_y = -1; +} + +void CHARACTER::on_direct_input(NETOBJ_PLAYER_INPUT *new_input) +{ + mem_copy(&latest_previnput, &latest_input, sizeof(latest_input)); + mem_copy(&latest_input, new_input, sizeof(latest_input)); + + if(num_inputs > 2 && team != -1) + { + handle_weaponswitch(); + fire_weapon(); + } +} + +void CHARACTER::tick() +{ + //input = latest_input; + + // grab latest input + /* + { + int size = 0; + int *input = server_latestinput(client_id, &size); + if(input) + { + mem_copy(&latest_previnput, &latest_input, sizeof(latest_input)); + mem_copy(&latest_input, input, sizeof(latest_input)); + } + }*/ + + // check if we have enough input + // this is to prevent initial weird clicks + /* + if(num_inputs < 2) + { + latest_previnput = latest_input; + previnput = input; + }*/ + + //game.world.core.players[player->client_id] = &core; + + // enable / disable physics + /* + if(team == -1 || dead) + { + game.world.core.players[client_id] = 0; + //game.world.remove_entity(this); + } + else + { + game.world.core.players[client_id] = &core; + //game.world._entity(this); + } + + // 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()/2 && count_input(latest_previnput.fire, latest_input.fire).presses) + die_tick = -1; + 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(); + + player_state = input.player_state; + + // Previnput + previnput = input; + return; +} + +void CHARACTER::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(player->client_id); + + if(events&COREEVENT_GROUND_JUMP) game.create_sound(pos, SOUND_PLAYER_JUMP, mask); + if(events&COREEVENT_AIR_JUMP) + { + game.create_sound(pos, SOUND_PLAYER_AIRJUMP, mask); + NETEVENT_COMMON *c = (NETEVENT_COMMON *)game.events.create(NETEVENTTYPE_AIRJUMP, sizeof(NETEVENT_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) game.create_sound(pos, SOUND_HOOK_ATTACH_PLAYER, cmask_all()); + if(events&COREEVENT_HOOK_ATTACH_GROUND) game.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; + } +} + +bool CHARACTER::increase_health(int amount) +{ + if(health >= 10) + return false; + health = clamp(health+amount, 0, 10); + return true; +} + +bool CHARACTER::increase_armor(int amount) +{ + if(armor >= 10) + return false; + armor = clamp(armor+amount, 0, 10); + return true; +} + +void CHARACTER::die(int killer, int weapon) +{ + /*if (dead || team == -1) + return;*/ + int mode_special = game.controller->on_character_death(this, &game.players[killer], weapon); + + dbg_msg("game", "kill killer='%d:%s' victim='%d:%s' weapon=%d special=%d", + killer, server_clientname(killer), + player->client_id, server_clientname(player->client_id), weapon, mode_special); + + // send the kill message + NETMSG_SV_KILLMSG msg; + msg.killer = killer; + msg.victim = player->client_id; + msg.weapon = weapon; + msg.mode_special = mode_special; + msg.pack(MSGFLAG_VITAL); + server_send_msg(-1); + + // a nice sound + game.create_sound(pos, SOUND_PLAYER_DIE); + + // set dead state + // TODO: do stuff here + /* + die_pos = pos; + dead = true; + die_tick = server_tick(); + */ + alive = false; + game.world.remove_entity(this); + game.create_death(pos, player->client_id); +} + +bool CHARACTER::take_damage(vec2 force, int dmg, int from, int weapon) +{ + core.vel += force; + + if(game.controller->is_friendly_fire(player->client_id, from) && !config.sv_teamdamage) + return false; + + // player only inflicts half damage on self + if(from == player->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 + game.create_damageind(pos, damage_taken*0.25f, dmg); + } + else + { + damage_taken = 0; + game.create_damageind(pos, 0, dmg); + } + + if(dmg) + { + if(armor) + { + if(dmg > 1) + { + health--; + dmg--; + } + + if(dmg > armor) + { + dmg -= armor; + armor = 0; + } + else + { + armor -= dmg; + dmg = 0; + } + } + + health -= dmg; + } + + damage_taken_tick = server_tick(); + + // do damage hit sound + if(from >= 0 && from != player->client_id) + game.create_sound(game.players[from].view_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 != player->client_id) + { + CHARACTER *chr = &game.players[from].character; + chr->emote_type = EMOTE_HAPPY; + chr->emote_stop = server_tick() + server_tickspeed(); + } + + return false; + } + + if (dmg > 2) + game.create_sound(pos, SOUND_PLAYER_PAIN_LONG); + else + game.create_sound(pos, SOUND_PLAYER_PAIN_SHORT); + + emote_type = EMOTE_PAIN; + emote_stop = server_tick() + 500 * server_tickspeed() / 1000; + + // spawn blood? + return true; +} + +void CHARACTER::snap(int snaping_client) +{ + if(distance(game.players[snaping_client].view_pos, pos) > 1000.0f) + return; + + NETOBJ_CHARACTER *character = (NETOBJ_CHARACTER *)snap_new_item(NETOBJTYPE_CHARACTER, player->client_id, sizeof(NETOBJ_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 != player->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 = 0; + character->health = 0; + character->armor = 0; + + character->weapon = active_weapon; + character->attacktick = attack_tick; + + character->wanted_direction = input.direction; + /* + if(input.left && !input.right) + character->wanted_direction = -1; + else if(!input.left && input.right) + character->wanted_direction = 1;*/ + + + if(player->client_id == snaping_client) + { + character->health = health; + character->armor = armor; + if(weapons[active_weapon].ammo > 0) + character->ammocount = weapons[active_weapon].ammo; + } + + if (character->emote == EMOTE_NORMAL) + { + if(250 - ((server_tick() - last_action)%(250)) < 5) + character->emote = EMOTE_BLINK; + } + + character->player_state = player_state; +} + +PLAYER::PLAYER() +{ +} + +void PLAYER::init(int client_id) +{ + // clear everything + mem_zero(this, sizeof(*this)); + new(this) PLAYER(); + this->client_id = client_id; +} + +void PLAYER::tick() +{ + server_setclientscore(client_id, score); + + // 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; + } + } + + if(spawning && !get_character()) + try_respawn(); + + if(get_character()) + view_pos = get_character()->pos; +} + +void PLAYER::snap(int snaping_client) +{ + NETOBJ_PLAYER_INFO *info = (NETOBJ_PLAYER_INFO *)snap_new_item(NETOBJTYPE_PLAYER_INFO, client_id, sizeof(NETOBJ_PLAYER_INFO)); + + info->latency = latency.min; + info->latency_flux = latency.max-latency.min; + info->local = 0; + info->cid = client_id; + info->score = score; + info->team = team; + + if(client_id == snaping_client) + info->local = 1; +} + +void PLAYER::on_disconnect() +{ + kill_character(); + + //game.controller->on_player_death(&game.players[client_id], 0, -1); + + char buf[512]; + str_format(buf, sizeof(buf), "%s has left the game", server_clientname(client_id)); + game.send_chat(-1, CHAT_ALL, buf); + + dbg_msg("game", "leave player='%d:%s'", client_id, server_clientname(client_id)); + + // clear this whole structure + init(-1); + + /*game.world.remove_entity(&game.players[client_id]); + game.world.core.players[client_id] = 0x0; + game.players[client_id].client_id = -1; */ +} + +void PLAYER::on_predicted_input(NETOBJ_PLAYER_INPUT *new_input) +{ + CHARACTER *chr = get_character(); + if(chr) + chr->on_predicted_input(new_input); +} + +void PLAYER::on_direct_input(NETOBJ_PLAYER_INPUT *new_input) +{ + CHARACTER *chr = get_character(); + if(chr) + chr->on_direct_input(new_input); + + if(!chr && team >= 0 && (new_input->fire&1)) + { + spawning = true; + dbg_msg("", "I wanna spawn"); + } +} + +CHARACTER *PLAYER::get_character() +{ + if(character.alive) + return &character; + return 0; +} + +void PLAYER::kill_character() +{ + CHARACTER *chr = get_character(); + if(chr) + chr->die(-1, -1); +} + +void PLAYER::respawn() +{ + spawning = true; +} + +void PLAYER::set_team(int new_team) +{ + // clamp the team + new_team = game.controller->clampteam(new_team); + if(team == new_team) + return; + + char buf[512]; + str_format(buf, sizeof(buf), "%s joined the %s", server_clientname(client_id), game.controller->get_team_name(new_team)); + game.send_chat(-1, CHAT_ALL, buf); + + kill_character(); + team = new_team; + score = 0; + dbg_msg("game", "team_join player='%d:%s' team=%d", client_id, server_clientname(client_id), team); + + game.controller->on_player_info_change(&game.players[client_id]); + + // send all info to this client + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(game.players[i].client_id != -1) + game.send_info(i, -1); + } +} + +vec2 spawn_points[3][64]; +int num_spawn_points[3] = {0}; + +struct SPAWNEVAL +{ + SPAWNEVAL() + { + got = false; + friendly_team = -1; +// die_pos = vec2(0,0); + pos = vec2(100,100); + } + + vec2 pos; + bool got; + int friendly_team; + float score; +// vec2 die_pos; +}; + +static float evaluate_spawn(SPAWNEVAL *eval, vec2 pos) +{ + float score = 0.0f; + CHARACTER *c = (CHARACTER *)game.world.find_first(NETOBJTYPE_CHARACTER); + for(; c; c = (CHARACTER *)c->typenext()) + { + // team mates are not as dangerous as enemies + float scoremod = 1.0f; + if(eval->friendly_team != -1 && c->team == eval->friendly_team) + scoremod = 0.5f; + + float d = distance(pos, c->pos); + if(d == 0) + score += 1000000000.0f; + else + score += 1.0f/d; + } + + // weight in the die posititon + /* + float d = distance(pos, eval->die_pos); + if(d == 0) + score += 1000000000.0f; + else + score += 1.0f/d;*/ + + return score; +} + +static void evaluate_spawn_type(SPAWNEVAL *eval, int t) +{ + // get spawn point + /* + int start, num; + map_get_type(t, &start, &num); + if(!num) + return; + */ + for(int i = 0; i < num_spawn_points[t]; i++) + { + //num_spawn_points[t] + //mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + i, NULL, NULL); + vec2 p = spawn_points[t][i];// vec2((float)sp->x, (float)sp->y); + float s = evaluate_spawn(eval, p); + if(!eval->got || eval->score > s) + { + eval->got = true; + eval->score = s; + eval->pos = p; + } + } +} + +void PLAYER::try_respawn() +{ + vec2 spawnpos = vec2(100.0f, -60.0f); + + // get spawn point + SPAWNEVAL eval; + //eval.die_pos = die_pos; + + eval.pos = vec2(100, 100); + + if(game.controller->gametype == GAMETYPE_CTF) + { + eval.friendly_team = team; + + // try first try own team spawn, then normal spawn and then enemy + evaluate_spawn_type(&eval, 1+(team&1)); + if(!eval.got) + { + evaluate_spawn_type(&eval, 0); + if(!eval.got) + evaluate_spawn_type(&eval, 1+((team+1)&1)); + } + } + else + { + if(game.controller->gametype == GAMETYPE_TDM) + eval.friendly_team = team; + + evaluate_spawn_type(&eval, 0); + evaluate_spawn_type(&eval, 1); + evaluate_spawn_type(&eval, 2); + } + + spawnpos = eval.pos; + + // check if the position is occupado + ENTITY *ents[2] = {0}; + int num_ents = game.world.find_entities(spawnpos, 64, ents, 2, NETOBJTYPE_CHARACTER); + + if(num_ents == 0) + { + spawning = false; + character.spawn(this, spawnpos, team); + } + + /* + pos = spawnpos; + + core.pos = pos; + core.vel = vec2(0,0); + core.hooked_player = -1; + + health = 10; + armor = 0; + jumped = 0; + + mem_zero(&ninja, sizeof(ninja)); + + dead = false; + player_state = PLAYERSTATE_PLAYING; + + game.world.insert_entity(this); + + 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 = 10; + + active_weapon = WEAPON_GUN; + last_weapon = WEAPON_HAMMER; + queued_weapon = 0; + + reload_timer = 0; + + // Create sound and spawn effects + game.create_sound(pos, SOUND_PLAYER_SPAWN); + game.create_playerspawn(pos); + + game.controller->on_player_spawn(player); + */ +} diff --git a/src/game/server/entities/character.hpp b/src/game/server/entities/character.hpp new file mode 100644 index 00000000..6ceb987c --- /dev/null +++ b/src/game/server/entities/character.hpp @@ -0,0 +1,122 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#ifndef GAME_SERVER_ENTITY_CHARACTER_H +#define GAME_SERVER_ENTITY_CHARACTER_H + +#include +#include +#include + +#include + +class CHARACTER : public ENTITY +{ +public: + // player controlling this character + class PLAYER *player; + + bool alive; + + // weapon info + ENTITY *hitobjects[10]; + int numobjectshit; + struct WEAPONSTAT + { + int ammoregenstart; + int ammo; + int ammocost; + bool got; + } weapons[NUM_WEAPONS]; + + int active_weapon; + int last_weapon; + int queued_weapon; + + int reload_timer; + int attack_tick; + + int damage_taken; + + int emote_type; + int emote_stop; + + // TODO: clean this up + char skin_name[64]; + int use_custom_color; + int color_body; + int color_feet; + + int last_action; // last tick that the player took any action ie some input + + // these are non-heldback inputs + NETOBJ_PLAYER_INPUT latest_previnput; + NETOBJ_PLAYER_INPUT latest_input; + + // input + NETOBJ_PLAYER_INPUT previnput; + NETOBJ_PLAYER_INPUT input; + int num_inputs; + int jumped; + + int damage_taken_tick; + + int health; + int armor; + + // ninja + struct + { + vec2 activationdir; + int activationtick; + int currentcooldown; + int currentmovetime; + } ninja; + + // + //int score; + int team; + int player_state; // if the client is chatting, accessing a menu or so + + // the player core for the physics + CHARACTER_CORE core; + + // + CHARACTER(); + + virtual void reset(); + virtual void destroy(); + + bool is_grounded(); + + void set_weapon(int w); + + void handle_weaponswitch(); + void do_weaponswitch(); + + int handle_weapons(); + int handle_ninja(); + + void on_predicted_input(NETOBJ_PLAYER_INPUT *new_input); + void on_direct_input(NETOBJ_PLAYER_INPUT *new_input); + void fire_weapon(); + + void die(int killer, int weapon); + + bool take_damage(vec2 force, int dmg, int from, int weapon); + + + bool spawn(PLAYER *player, vec2 pos, int team); + //bool init_tryspawn(int team); + bool remove(); + + static const int phys_size = 28; + + virtual void tick(); + virtual void tick_defered(); + virtual void snap(int snaping_client); + + bool increase_health(int amount); + bool increase_armor(int amount); +}; + +#endif diff --git a/src/game/server/entities/laser.cpp b/src/game/server/entities/laser.cpp new file mode 100644 index 00000000..cedf7850 --- /dev/null +++ b/src/game/server/entities/laser.cpp @@ -0,0 +1,112 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include "laser.hpp" + +////////////////////////////////////////////////// +// laser +////////////////////////////////////////////////// +LASER::LASER(vec2 pos, vec2 direction, float start_energy, CHARACTER *owner) +: ENTITY(NETOBJTYPE_LASER) +{ + this->pos = pos; + this->owner = owner; + energy = start_energy; + dir = direction; + bounces = 0; + do_bounce(); + + game.world.insert_entity(this); +} + + +bool LASER::hit_character(vec2 from, vec2 to) +{ + vec2 at; + CHARACTER *hit = game.world.intersect_character(pos, to, 0.0f, at, owner); + if(!hit) + return false; + + this->from = from; + pos = at; + energy = -1; + hit->take_damage(vec2(0,0), tuning.laser_damage, owner->player->client_id, WEAPON_RIFLE); + return true; +} + +void LASER::do_bounce() +{ + eval_tick = server_tick(); + + if(energy < 0) + { + //dbg_msg("laser", "%d removed", server_tick()); + game.world.destroy_entity(this); + return; + } + + vec2 to = pos + dir*energy; + + if(col_intersect_line(pos, to, &to)) + { + if(!hit_character(pos, to)) + { + // intersected + from = pos; + pos = to - dir*2; + vec2 temp_pos = pos; + vec2 temp_dir = dir*4.0f; + + move_point(&temp_pos, &temp_dir, 1.0f, 0); + pos = temp_pos; + dir = normalize(temp_dir); + + energy -= distance(from, pos) + tuning.laser_bounce_cost; + bounces++; + + if(bounces > tuning.laser_bounce_num) + energy = -1; + + game.create_sound(pos, SOUND_RIFLE_BOUNCE); + } + } + else + { + if(!hit_character(pos, to)) + { + from = pos; + pos = to; + energy = -1; + } + } + + //dbg_msg("laser", "%d done %f %f %f %f", server_tick(), from.x, from.y, pos.x, pos.y); +} + +void LASER::reset() +{ + game.world.destroy_entity(this); +} + +void LASER::tick() +{ + if(server_tick() > eval_tick+(server_tickspeed()*tuning.laser_bounce_delay)/1000.0f) + { + do_bounce(); + } + +} + +void LASER::snap(int snapping_client) +{ + if(distance(game.players[snapping_client].view_pos, pos) > 1000.0f) + return; + + NETOBJ_LASER *obj = (NETOBJ_LASER *)snap_new_item(NETOBJTYPE_LASER, id, sizeof(NETOBJ_LASER)); + obj->x = (int)pos.x; + obj->y = (int)pos.y; + obj->from_x = (int)from.x; + obj->from_y = (int)from.y; + obj->start_tick = eval_tick; +} diff --git a/src/game/server/entities/laser.hpp b/src/game/server/entities/laser.hpp new file mode 100644 index 00000000..4842b3f8 --- /dev/null +++ b/src/game/server/entities/laser.hpp @@ -0,0 +1,31 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#ifndef GAME_SERVER_ENTITY_LASER_H +#define GAME_SERVER_ENTITY_LASER_H + +#include + +class CHARACTER; + +class LASER : public ENTITY +{ + vec2 from; + vec2 dir; + float energy; + int bounces; + int eval_tick; + CHARACTER *owner; + + bool hit_character(vec2 from, vec2 to); + void do_bounce(); + +public: + + LASER(vec2 pos, vec2 direction, float start_energy, CHARACTER *owner); + + virtual void reset(); + virtual void tick(); + virtual void snap(int snapping_client); +}; + +#endif diff --git a/src/game/server/entities/pickup.cpp b/src/game/server/entities/pickup.cpp new file mode 100644 index 00000000..a4a3a2c9 --- /dev/null +++ b/src/game/server/entities/pickup.cpp @@ -0,0 +1,142 @@ +#include +#include +#include +#include "pickup.hpp" + +////////////////////////////////////////////////// +// pickup +////////////////////////////////////////////////// +PICKUP::PICKUP(int _type, int _subtype) +: ENTITY(NETOBJTYPE_PICKUP) +{ + type = _type; + subtype = _subtype; + proximity_radius = phys_size; + + reset(); + + // TODO: should this be done here? + game.world.insert_entity(this); +} + +void PICKUP::reset() +{ + if (data->pickups[type].spawndelay > 0) + spawntick = server_tick() + server_tickspeed() * data->pickups[type].spawndelay; + else + spawntick = -1; +} + +void PICKUP::tick() +{ + // wait for respawn + if(spawntick > 0) + { + if(server_tick() > spawntick) + { + // respawn + spawntick = -1; + + if(type == POWERUP_WEAPON) + game.create_sound(pos, SOUND_WEAPON_SPAWN); + } + else + return; + } + // Check if a player intersected us + CHARACTER *chr = game.world.closest_character(pos, 20.0f, 0); + if(chr) + { + // player picked us up, is someone was hooking us, let them go + int respawntime = -1; + switch (type) + { + case POWERUP_HEALTH: + if(chr->increase_health(1)) + { + game.create_sound(pos, SOUND_PICKUP_HEALTH); + respawntime = data->pickups[type].respawntime; + } + break; + case POWERUP_ARMOR: + if(chr->increase_armor(1)) + { + game.create_sound(pos, SOUND_PICKUP_ARMOR); + respawntime = data->pickups[type].respawntime; + } + break; + + case POWERUP_WEAPON: + if(subtype >= 0 && subtype < NUM_WEAPONS) + { + if(chr->weapons[subtype].ammo < data->weapons.id[subtype].maxammo || !chr->weapons[subtype].got) + { + chr->weapons[subtype].got = true; + chr->weapons[subtype].ammo = min(data->weapons.id[subtype].maxammo, chr->weapons[subtype].ammo + 10); + respawntime = data->pickups[type].respawntime; + + // TODO: data compiler should take care of stuff like this + if(subtype == WEAPON_GRENADE) + game.create_sound(pos, SOUND_PICKUP_GRENADE); + else if(subtype == WEAPON_SHOTGUN) + game.create_sound(pos, SOUND_PICKUP_SHOTGUN); + else if(subtype == WEAPON_RIFLE) + game.create_sound(pos, SOUND_PICKUP_SHOTGUN); + + if(chr->player) + game.send_weapon_pickup(chr->player->client_id, subtype); + } + } + break; + case POWERUP_NINJA: + { + // activate ninja on target player + chr->ninja.activationtick = server_tick(); + chr->weapons[WEAPON_NINJA].got = true; + chr->last_weapon = chr->active_weapon; + chr->active_weapon = WEAPON_NINJA; + respawntime = data->pickups[type].respawntime; + game.create_sound(pos, SOUND_PICKUP_NINJA); + + // loop through all players, setting their emotes + ENTITY *ents[64]; + int num = game.world.find_entities(vec2(0, 0), 1000000, ents, 64, NETOBJTYPE_CHARACTER); + for (int i = 0; i < num; i++) + { + CHARACTER *c = (CHARACTER *)ents[i]; + if (c != chr) + { + c->emote_type = EMOTE_SURPRISE; + c->emote_stop = server_tick() + server_tickspeed(); + } + } + + chr->emote_type = EMOTE_ANGRY; + chr->emote_stop = server_tick() + 1200 * server_tickspeed() / 1000; + + break; + } + default: + break; + }; + + if(respawntime >= 0) + { + dbg_msg("game", "pickup player='%d:%s' item=%d/%d", + chr->player->client_id, server_clientname(chr->player->client_id), type, subtype); + spawntick = server_tick() + server_tickspeed() * respawntime; + } + } +} + +void PICKUP::snap(int snapping_client) +{ + if(spawntick != -1) + return; + + NETOBJ_PICKUP *up = (NETOBJ_PICKUP *)snap_new_item(NETOBJTYPE_PICKUP, id, sizeof(NETOBJ_PICKUP)); + up->x = (int)pos.x; + up->y = (int)pos.y; + up->type = type; // TODO: two diffrent types? what gives? + up->subtype = subtype; +} diff --git a/src/game/server/entities/pickup.hpp b/src/game/server/entities/pickup.hpp new file mode 100644 index 00000000..cd480d92 --- /dev/null +++ b/src/game/server/entities/pickup.hpp @@ -0,0 +1,24 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#ifndef GAME_SERVER_ENTITY_PICKUP_H +#define GAME_SERVER_ENTITY_PICKUP_H + +#include + +// TODO: move to seperate file +class PICKUP : public ENTITY +{ +public: + static const int phys_size = 14; + + int type; + int subtype; // weapon type for instance? + int spawntick; + PICKUP(int _type, int _subtype = 0); + + virtual void reset(); + virtual void tick(); + virtual void snap(int snapping_client); +}; + +#endif diff --git a/src/game/server/entities/projectile.cpp b/src/game/server/entities/projectile.cpp new file mode 100644 index 00000000..d258e69e --- /dev/null +++ b/src/game/server/entities/projectile.cpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include "projectile.hpp" + + +////////////////////////////////////////////////// +// projectile +////////////////////////////////////////////////// +PROJECTILE::PROJECTILE(int type, int owner, vec2 pos, vec2 dir, int span, ENTITY* powner, + int damage, int flags, float force, int sound_impact, int weapon) +: ENTITY(NETOBJTYPE_PROJECTILE) +{ + this->type = type; + this->pos = pos; + this->direction = dir; + 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(); + game.world.insert_entity(this); +} + +void PROJECTILE::reset() +{ + game.world.destroy_entity(this); +} + +vec2 PROJECTILE::get_pos(float time) +{ + float curvature = 0; + float speed = 0; + if(type == WEAPON_GRENADE) + { + curvature = tuning.grenade_curvature; + speed = tuning.grenade_speed; + } + else if(type == WEAPON_SHOTGUN) + { + curvature = tuning.shotgun_curvature; + speed = tuning.shotgun_speed; + } + else if(type == WEAPON_GUN) + { + curvature = tuning.gun_curvature; + speed = tuning.gun_speed; + } + + return calc_pos(pos, direction, curvature, speed, time); +} + + +void PROJECTILE::tick() +{ + + float pt = (server_tick()-start_tick-1)/(float)server_tickspeed(); + float ct = (server_tick()-start_tick)/(float)server_tickspeed(); + vec2 prevpos = get_pos(pt); + vec2 curpos = get_pos(ct); + + lifespan--; + + int collide = col_intersect_line(prevpos, curpos, &curpos); + //int collide = col_check_point((int)curpos.x, (int)curpos.y); + + CHARACTER *targetchr = game.world.intersect_character(prevpos, curpos, 6.0f, curpos, powner); + if(targetchr || collide || lifespan < 0) + { + if(lifespan >= 0 || weapon == WEAPON_GRENADE) + game.create_sound(curpos, sound_impact); + + if(flags & PROJECTILE_FLAGS_EXPLODE) + game.create_explosion(curpos, owner, weapon, false); + else if(targetchr) + { + targetchr->take_damage(direction * max(0.001f, force), damage, owner, weapon); + } + + game.world.destroy_entity(this); + } +} + +void PROJECTILE::fill_info(NETOBJ_PROJECTILE *proj) +{ + proj->x = (int)pos.x; + proj->y = (int)pos.y; + proj->vx = (int)(direction.x*100.0f); + proj->vy = (int)(direction.y*100.0f); + proj->start_tick = start_tick; + proj->type = type; +} + +void PROJECTILE::snap(int snapping_client) +{ + float ct = (server_tick()-start_tick)/(float)server_tickspeed(); + + if(distance(game.players[snapping_client].view_pos, get_pos(ct)) > 1000.0f) + return; + + NETOBJ_PROJECTILE *proj = (NETOBJ_PROJECTILE *)snap_new_item(NETOBJTYPE_PROJECTILE, id, sizeof(NETOBJ_PROJECTILE)); + fill_info(proj); +} diff --git a/src/game/server/entities/projectile.hpp b/src/game/server/entities/projectile.hpp new file mode 100644 index 00000000..c1370af1 --- /dev/null +++ b/src/game/server/entities/projectile.hpp @@ -0,0 +1,38 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#ifndef GAME_SERVER_ENTITY_PROJECTILE_H +#define GAME_SERVER_ENTITY_PROJECTILE_H + +class PROJECTILE : public ENTITY +{ +public: + enum + { + PROJECTILE_FLAGS_EXPLODE = 1 << 0, + }; + + vec2 direction; + ENTITY *powner; // this is nasty, could be removed when client quits + int lifespan; + int owner; + int type; + int flags; + int damage; + int sound_impact; + int weapon; + int bounce; + float force; + int start_tick; + + PROJECTILE(int type, int owner, vec2 pos, vec2 vel, int span, ENTITY* powner, + int damage, int flags, float force, int sound_impact, int weapon); + + vec2 get_pos(float time); + void fill_info(NETOBJ_PROJECTILE *proj); + + virtual void reset(); + virtual void tick(); + virtual void snap(int snapping_client); +}; + +#endif diff --git a/src/game/server/entity.hpp b/src/game/server/entity.hpp new file mode 100644 index 00000000..d3ae3a1c --- /dev/null +++ b/src/game/server/entity.hpp @@ -0,0 +1,83 @@ +#ifndef GAME_SERVER_ENTITY_H +#define GAME_SERVER_ENTITY_H + +#include + +/* + Class: Entity + Basic entity class. +*/ +class ENTITY +{ +private: + friend class GAMEWORLD; // thy these? + ENTITY *prev_entity; + ENTITY *next_entity; + + ENTITY *prev_type_entity; + ENTITY *next_type_entity; +protected: + bool marked_for_destroy; + int id; + int objtype; +public: + + ENTITY(int objtype); + virtual ~ENTITY(); + + ENTITY *typenext() { return next_type_entity; } + ENTITY *typeprev() { return prev_type_entity; } + + /* + Function: destroy + Destorys the entity. + */ + virtual void destroy() { delete this; } + + /* + Function: reset + Called when the game resets the map. Puts the entity + back to it's starting state or perhaps destroys it. + */ + virtual void reset() {} + + /* + Function: tick + Called progress the entity to the next tick. Updates + and moves the entity to it's new state and position. + */ + virtual void tick() {} + + /* + Function: tick_defered + Called after all entities tick() function has been called. + */ + virtual void tick_defered() {} + + /* + Function: snap + Called when a new snapshot is being generated for a specific + client. + + Arguments: + snapping_client - ID of the client which snapshot is + being generated. Could be -1 to create a complete + snapshot of everything in the game for demo + recording. + */ + virtual void snap(int snapping_client) {} + + /* + Variable: proximity_radius + Contains the physical size of the entity. + */ + float proximity_radius; + + /* + Variable: pos + Contains the current posititon of the entity. + */ + vec2 pos; +}; + +#endif diff --git a/src/game/server/eventhandler.hpp b/src/game/server/eventhandler.hpp new file mode 100644 index 00000000..4d513154 --- /dev/null +++ b/src/game/server/eventhandler.hpp @@ -0,0 +1,25 @@ +#ifndef GAME_SERVER_EVENTHANDLER_H +#define GAME_SERVER_EVENTHANDLER_H + +// +class EVENTHANDLER +{ + static const int MAX_EVENTS = 128; + static const int MAX_DATASIZE = 128*64; + + int types[MAX_EVENTS]; // TODO: remove some of these arrays + int offsets[MAX_EVENTS]; + int sizes[MAX_EVENTS]; + int client_masks[MAX_EVENTS]; + char data[MAX_DATASIZE]; + + int current_offset; + int num_events; +public: + EVENTHANDLER(); + void *create(int type, int size, int mask = -1); + void clear(); + void snap(int snapping_client); +}; + +#endif diff --git a/src/game/server/gamecontext.hpp b/src/game/server/gamecontext.hpp new file mode 100644 index 00000000..c4035b3f --- /dev/null +++ b/src/game/server/gamecontext.hpp @@ -0,0 +1,38 @@ + +#include "eventhandler.hpp" +#include "gamecontroller.hpp" +#include "gameworld.hpp" + +class GAMECONTEXT +{ +public: + GAMECONTEXT(); + void clear(); + + EVENTHANDLER events; + PLAYER players[MAX_CLIENTS]; + + GAMECONTROLLER *controller; + GAMEWORLD world; + + void tick(); + void snap(int client_id); + + // helper functions + 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_playerspawn(vec2 p); + void create_death(vec2 p, int who); + void create_sound(vec2 pos, int sound, int mask=-1); + void create_sound_global(int sound, int target=-1); + + // network + void send_chat(int cid, int team, const char *text); + void send_emoticon(int cid, int emoticon); + void send_weapon_pickup(int cid, int weapon); + void send_broadcast(const char *text, int cid); + void send_info(int who, int to_who); +}; + +extern GAMECONTEXT game; diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp new file mode 100644 index 00000000..aeab559c --- /dev/null +++ b/src/game/server/gamecontroller.cpp @@ -0,0 +1,418 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include +#include "gs_common.hpp" + +#include "entities/pickup.hpp" + +GAMECONTROLLER::GAMECONTROLLER() +{ + // 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; +} + +// UGLY!!!! +extern vec2 spawn_points[3][64]; +extern int num_spawn_points[3]; + +bool GAMECONTROLLER::on_entity(int index, vec2 pos) +{ + int type = -1; + int subtype = 0; + + if(index == ENTITY_SPAWN) + spawn_points[0][num_spawn_points[0]++] = pos; + else if(index == ENTITY_SPAWN_RED) + spawn_points[1][num_spawn_points[1]++] = pos; + else if(index == ENTITY_SPAWN_BLUE) + spawn_points[2][num_spawn_points[2]++] = pos; + else if(index == ENTITY_ARMOR_1) + type = POWERUP_ARMOR; + else if(index == ENTITY_HEALTH_1) + type = POWERUP_HEALTH; + else if(index == ENTITY_WEAPON_SHOTGUN) + { + type = POWERUP_WEAPON; + subtype = WEAPON_SHOTGUN; + } + else if(index == ENTITY_WEAPON_GRENADE) + { + type = POWERUP_WEAPON; + subtype = WEAPON_GRENADE; + } + else if(index == ENTITY_WEAPON_RIFLE) + { + type = POWERUP_WEAPON; + subtype = WEAPON_RIFLE; + } + else if(index == ENTITY_POWERUP_NINJA && config.sv_powerups) + { + type = POWERUP_NINJA; + subtype = WEAPON_NINJA; + } + + if(type != -1) + { + PICKUP *pickup = new PICKUP(type, subtype); + pickup->pos = pos; + return true; + } + + return false; +} + +void GAMECONTROLLER::endround() +{ + if(warmup) // game can't end when we are running warmup + return; + + game.world.paused = true; + game_over_tick = server_tick(); + sudden_death = 0; +} + +void GAMECONTROLLER::resetgame() +{ + game.world.reset_requested = true; +} + +const char *GAMECONTROLLER::get_team_name(int team) +{ + if(is_teamplay) + { + if(team == 0) + return "red team"; + else if(team == 1) + return "blue team"; + } + else + { + if(team == 0) + return "game"; + } + + return "spectators"; +} + +static bool is_separator(char c) { return c == ';' || c == ' ' || c == ',' || c == '\t'; } + +void GAMECONTROLLER::startround() +{ + resetgame(); + + round_start_tick = server_tick(); + sudden_death = 0; + game_over_tick = -1; + game.world.paused = false; + teamscore[0] = 0; + teamscore[1] = 0; + round_count++; +} + +void GAMECONTROLLER::cyclemap() +{ + if(!strlen(config.sv_maprotation)) + return; + + if(round_count < config.sv_rounds_per_map-1) + return; + + // handle maprotation + const char *map_rotation = config.sv_maprotation; + const char *current_map = config.sv_map; + + int current_map_len = strlen(current_map); + const char *next_map = map_rotation; + while(*next_map) + { + int wordlen = 0; + while(next_map[wordlen] && !is_separator(next_map[wordlen])) + wordlen++; + + if(wordlen == current_map_len && strncmp(next_map, current_map, current_map_len) == 0) + { + // map found + next_map += current_map_len; + while(*next_map && is_separator(*next_map)) + next_map++; + + break; + } + + next_map++; + } + + // restart rotation + if(next_map[0] == 0) + next_map = map_rotation; + + // cut out the next map + char buf[512]; + for(int i = 0; i < 512; i++) + { + buf[i] = next_map[i]; + if(is_separator(next_map[i]) || next_map[i] == 0) + { + buf[i] = 0; + break; + } + } + + // skip spaces + int i = 0; + while(is_separator(buf[i])) + i++; + + dbg_msg("game", "rotating map to %s", &buf[i]); + str_copy(config.sv_map, &buf[i], sizeof(config.sv_map)); +} + +void GAMECONTROLLER::post_reset() +{ + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(game.players[i].client_id != -1) + game.players[i].respawn(); + } +} + +void GAMECONTROLLER::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 GAMECONTROLLER::on_character_death(class CHARACTER *victim, class PLAYER *killer, int weapon) +{ + // do scoreing + if(!killer) + return 0; + if(killer == victim->player) + victim->player->score--; // suicide + else + { + if(is_teamplay && victim->team == killer->team) + killer->score--; // teamkill + else + killer->score++; // normal kill + } + return 0; +} + +void GAMECONTROLLER::do_warmup(int seconds) +{ + warmup = seconds*server_tickspeed(); +} + +bool GAMECONTROLLER::is_friendly_fire(int cid1, int cid2) +{ + if(cid1 == cid2) + return false; + + if(is_teamplay) + { + if(game.players[cid1].team == game.players[cid2].team) + return true; + } + + return false; +} + +void GAMECONTROLLER::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(game.players[i].client_id != -1) + prog = max(prog, (game.players[i].score*100)/config.sv_scorelimit); + } + } + } + + if(warmup) + prog = -1; + + server_setbrowseinfo(gametype, prog); +} + +void GAMECONTROLLER::snap(int snapping_client) +{ + NETOBJ_GAME *gameobj = (NETOBJ_GAME *)snap_new_item(NETOBJTYPE_GAME, 0, sizeof(NETOBJ_GAME)); + gameobj->paused = game.world.paused; + gameobj->game_over = game_over_tick==-1?0:1; + gameobj->sudden_death = sudden_death; + + gameobj->score_limit = config.sv_scorelimit; + gameobj->time_limit = config.sv_timelimit; + gameobj->round_start_tick = round_start_tick; + gameobj->gametype = gametype; + + gameobj->warmup = warmup; + + gameobj->teamscore_red = teamscore[0]; + gameobj->teamscore_blue = teamscore[1]; +} + +int GAMECONTROLLER::get_auto_team(int notthisid) +{ + int numplayers[2] = {0,0}; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(game.players[i].client_id != -1 && game.players[i].client_id != notthisid) + { + if(game.players[i].team == 0 || game.players[i].team == 1) + numplayers[game.players[i].team]++; + } + } + + int team = 0; + if(is_teamplay) + team = numplayers[0] > numplayers[1] ? 1 : 0; + + if(can_join_team(team, notthisid)) + return team; + return -1; +} + +bool GAMECONTROLLER::can_join_team(int team, int notthisid) +{ + (void)team; + int numplayers[2] = {0,0}; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(game.players[i].client_id != -1 && game.players[i].client_id != notthisid) + { + if(game.players[i].team >= 0 || game.players[i].team == 1) + numplayers[game.players[i].team]++; + } + } + + return (numplayers[0] + numplayers[1]) < config.sv_max_clients-config.sv_spectator_slots; +} + +void GAMECONTROLLER::do_player_score_wincheck() +{ + if(game_over_tick == -1 && !warmup) + { + // gather some stats + int topscore = 0; + int topscore_count = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(game.players[i].client_id != -1) + { + if(game.players[i].score > topscore) + { + topscore = game.players[i].score; + topscore_count = 1; + } + else if(game.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; + } + } +} + +void GAMECONTROLLER::do_team_score_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; + } + } +} + +int GAMECONTROLLER::clampteam(int team) +{ + if(team < 0) // spectator + return -1; + if(is_teamplay) + return team&1; + return 0; +} + +GAMECONTROLLER *gamecontroller = 0; diff --git a/src/game/server/gamecontroller.hpp b/src/game/server/gamecontroller.hpp new file mode 100644 index 00000000..a3cfe9ba --- /dev/null +++ b/src/game/server/gamecontroller.hpp @@ -0,0 +1,97 @@ +#ifndef GAME_SERVER_GAMECONTROLLER_H +#define GAME_SERVER_GAMECONTROLLER_H + +#include + +/* + Class: Game Controller + Controls the main game logic. Keeping track of team and player score, + winning conditions and specific game logic. +*/ +class GAMECONTROLLER +{ +protected: + void cyclemap(); + void resetgame(); + + int round_start_tick; + int game_over_tick; + int sudden_death; + + int teamscore[2]; + + int warmup; + int round_count; + + bool is_teamplay; + +public: + int gametype; + GAMECONTROLLER(); + + void do_team_score_wincheck(); + void do_player_score_wincheck(); + + void do_warmup(int seconds); + + void startround(); + void endround(); + + bool is_friendly_fire(int cid1, int cid2); + + /* + + */ + virtual void tick(); + + virtual void snap(int snapping_client); + + /* + Function: on_entity + Called when the map is loaded to process an entity + in the map. + + Arguments: + index - Entity index. + pos - Where the entity is located in the world. + + Returns: + bool? + */ + virtual bool on_entity(int index, vec2 pos); + + /* + Function: on_character_spawn + Called when a character spawns into the game world. + + Arguments: + chr - The character that was spawned. + */ + virtual void on_character_spawn(class CHARACTER *chr) {} + + /* + Function: on_character_death + Called when a character in the world dies. + + Arguments: + victim - The character that died. + killer - The player that killed it. + weapon - What weapon that killed it. Can be -1 for undefined + weapon when switching team or player suicides. + */ + virtual int on_character_death(class CHARACTER *victim, class PLAYER *killer, int weapon); + + virtual void on_player_info_change(class PLAYER *p); + + /* + + */ + virtual const char *get_team_name(int team); + virtual int get_auto_team(int notthisid); + virtual bool can_join_team(int team, int notthisid); + int clampteam(int team); + + virtual void post_reset(); +}; + +#endif diff --git a/src/game/server/gamemodes/ctf.cpp b/src/game/server/gamemodes/ctf.cpp new file mode 100644 index 00000000..4733ae82 --- /dev/null +++ b/src/game/server/gamemodes/ctf.cpp @@ -0,0 +1,200 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include +#include +#include "ctf.hpp" + +GAMECONTROLLER_CTF::GAMECONTROLLER_CTF() +{ + flags[0] = 0; + flags[1] = 0; + is_teamplay = true; +} + +bool GAMECONTROLLER_CTF::on_entity(int index, vec2 pos) +{ + if(GAMECONTROLLER::on_entity(index, pos)) + return true; + + int team = -1; + if(index == ENTITY_FLAGSTAND_RED) team = 0; + if(index == ENTITY_FLAGSTAND_BLUE) team = 1; + if(team == -1) + return false; + + FLAG *f = new FLAG(team); + f->stand_pos = pos; + f->pos = pos; + flags[team] = f; + return true; +} + +int GAMECONTROLLER_CTF::on_character_death(class CHARACTER *victim, class PLAYER *killer, int weaponid) +{ + GAMECONTROLLER::on_character_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_character == &killer->character) + had_flag |= 2; + if(f && f->carrying_character == victim) + { + game.create_sound_global(SOUND_CTF_DROP); + f->drop_tick = server_tick(); + f->carrying_character = 0; + f->vel = vec2(0,0); + + if(killer && killer->team != victim->team) + killer->score++; + + had_flag |= 1; + } + } + + return had_flag; +} + +void GAMECONTROLLER_CTF::tick() +{ + GAMECONTROLLER::tick(); + + do_team_score_wincheck(); + + for(int fi = 0; fi < 2; fi++) + { + FLAG *f = flags[fi]; + + if(!f) + continue; + + // + if(f->carrying_character) + { + // update flag position + f->pos = f->carrying_character->pos; + + if(flags[fi^1] && flags[fi^1]->at_stand) + { + if(distance(f->pos, flags[fi^1]->pos) < 32) + { + // CAPTURE! \o/ + teamscore[fi^1] += 100; + f->carrying_character->player->score += 5; + + dbg_msg("game", "flag_capture player='%d:%s'", + f->carrying_character->player->client_id, + server_clientname(f->carrying_character->player->client_id)); + + for(int i = 0; i < 2; i++) + flags[i]->reset(); + + game.create_sound_global(SOUND_CTF_CAPTURE); + } + } + } + else + { + CHARACTER *close_characters[MAX_CLIENTS]; + int num = game.world.find_entities(f->pos, 32.0f, (ENTITY**)close_characters, MAX_CLIENTS, NETOBJTYPE_CHARACTER); + for(int i = 0; i < num; i++) + { + if(close_characters[i]->team == f->team) + { + // return the flag + if(!f->at_stand) + { + CHARACTER *chr = close_characters[i]; + chr->player->score += 1; + + dbg_msg("game", "flag_return player='%d:%s'", + chr->player->client_id, + server_clientname(chr->player->client_id)); + + game.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_character = close_characters[i]; + f->carrying_character->player->score += 1; + + dbg_msg("game", "flag_grab player='%d:%s'", + f->carrying_character->player->client_id, + server_clientname(f->carrying_character->player->client_id)); + + for(int c = 0; c < MAX_CLIENTS; c++) + { + if(game.players[c].client_id == -1) + continue; + + if(game.players[c].team == fi) + game.create_sound_global(SOUND_CTF_GRAB_EN, game.players[c].client_id); + else + game.create_sound_global(SOUND_CTF_GRAB_PL, game.players[c].client_id); + } + break; + } + } + + if(!f->carrying_character && !f->at_stand) + { + if(server_tick() > f->drop_tick + server_tickspeed()*30) + { + game.create_sound_global(SOUND_CTF_RETURN); + f->reset(); + } + else + { + f->vel.y += game.world.core.tuning.gravity; + move_box(&f->pos, &f->vel, vec2(f->phys_size, f->phys_size), 0.5f); + } + } + } + } +} + +// Flag +FLAG::FLAG(int _team) +: ENTITY(NETOBJTYPE_FLAG) +{ + team = _team; + proximity_radius = phys_size; + carrying_character = 0x0; + + reset(); + + // TODO: should this be done here? + game.world.insert_entity(this); +} + +void FLAG::reset() +{ + carrying_character = 0; + at_stand = 1; + pos = stand_pos; + vel = vec2(0,0); +} + +void FLAG::snap(int snapping_client) +{ + NETOBJ_FLAG *flag = (NETOBJ_FLAG *)snap_new_item(NETOBJTYPE_FLAG, team, sizeof(NETOBJ_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_character) + flag->carried_by = carrying_character->player->client_id; +} diff --git a/src/game/server/gamemodes/ctf.hpp b/src/game/server/gamemodes/ctf.hpp new file mode 100644 index 00000000..636f9f38 --- /dev/null +++ b/src/game/server/gamemodes/ctf.hpp @@ -0,0 +1,35 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#include +#include + +class GAMECONTROLLER_CTF : public GAMECONTROLLER +{ +public: + class FLAG *flags[2]; + + GAMECONTROLLER_CTF(); + virtual void tick(); + + virtual bool on_entity(int index, vec2 pos); + virtual int on_character_death(class CHARACTER *victim, class PLAYER *killer, int weapon); +}; + +// TODO: move to seperate file +class FLAG : public ENTITY +{ +public: + static const int phys_size = 14; + CHARACTER *carrying_character; + 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/gamemodes/dm.cpp b/src/game/server/gamemodes/dm.cpp new file mode 100644 index 00000000..b38d18f6 --- /dev/null +++ b/src/game/server/gamemodes/dm.cpp @@ -0,0 +1,8 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include "dm.hpp" + +void GAMECONTROLLER_DM::tick() +{ + do_player_score_wincheck(); + GAMECONTROLLER::tick(); +} diff --git a/src/game/server/gamemodes/dm.hpp b/src/game/server/gamemodes/dm.hpp new file mode 100644 index 00000000..f57fe06d --- /dev/null +++ b/src/game/server/gamemodes/dm.hpp @@ -0,0 +1,9 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#include + +class GAMECONTROLLER_DM : public GAMECONTROLLER +{ +public: + virtual void tick(); +}; diff --git a/src/game/server/gamemodes/tdm.cpp b/src/game/server/gamemodes/tdm.cpp new file mode 100644 index 00000000..26441c9f --- /dev/null +++ b/src/game/server/gamemodes/tdm.cpp @@ -0,0 +1,31 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ +#include +#include +#include +#include "tdm.hpp" + +GAMECONTROLLER_TDM::GAMECONTROLLER_TDM() +{ + is_teamplay = true; +} + +int GAMECONTROLLER_TDM::on_character_death(class CHARACTER *victim, class PLAYER *killer, int weapon) +{ + GAMECONTROLLER::on_character_death(victim, killer, weapon); + + if(weapon >= 0) + { + // do team scoring + if(killer == victim->player) + teamscore[killer->team&1]--; // klant arschel + else + teamscore[killer->team&1]++; // good shit + } + return 0; +} + +void GAMECONTROLLER_TDM::tick() +{ + do_team_score_wincheck(); + GAMECONTROLLER::tick(); +} diff --git a/src/game/server/gamemodes/tdm.hpp b/src/game/server/gamemodes/tdm.hpp new file mode 100644 index 00000000..51c47ca5 --- /dev/null +++ b/src/game/server/gamemodes/tdm.hpp @@ -0,0 +1,12 @@ +/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ + +#include + +class GAMECONTROLLER_TDM : public GAMECONTROLLER +{ +public: + GAMECONTROLLER_TDM(); + + int on_character_death(class CHARACTER *victim, class PLAYER *killer, int weapon); + virtual void tick(); +}; diff --git a/src/game/server/gameworld.hpp b/src/game/server/gameworld.hpp new file mode 100644 index 00000000..76b39d23 --- /dev/null +++ b/src/game/server/gameworld.hpp @@ -0,0 +1,126 @@ + +class CHARACTER; + +/* + Class: Game World + Tracks all entities in the game. Propagates tick and + snap calls to all entities. +*/ +class GAMEWORLD +{ + void reset(); + void remove_entities(); + + enum + { + NUM_ENT_TYPES=10, // TODO: are more exact value perhaps? :) + }; + + // TODO: two lists seams kinda not good, shouldn't be needed + ENTITY *first_entity; + ENTITY *first_entity_types[NUM_ENT_TYPES]; + +public: + bool reset_requested; + bool paused; + WORLD_CORE core; + + GAMEWORLD(); + ~GAMEWORLD(); + + ENTITY *find_first() { return first_entity; } + ENTITY *find_first(int type); + + /* + Function: find_entities + Finds entities close to a position and returns them in a list. + + Arguments: + pos - Position. + radius - How close the entities have to be. + ents - Pointer to a list that should be filled with the pointers + to the entities. + max - Number of entities that fits into the ents array. + type - Type of the entities to find. -1 for all types. + + Returns: + Number of entities found and added to the ents array. + */ + int find_entities(vec2 pos, float radius, ENTITY **ents, int max, int type = -1); + + /* + Function: interserct_character + Finds the closest character that intersects the line. + + Arguments: + pos0 - Start position + pos2 - End position + radius - How for from the line the character is allowed to be. + new_pos - Intersection position + notthis - Entity to ignore intersecting with + + Returns: + Returns a pointer to the closest hit or NULL of there is no intersection. + */ + class CHARACTER *intersect_character(vec2 pos0, vec2 pos1, float radius, vec2 &new_pos, class ENTITY *notthis = 0); + + /* + Function: closest_character + Finds the closest character to a specific point. + + Arguments: + pos - The center position. + radius - How far off the character is allowed to be + notthis - Entity to ignore + + Returns: + Returns a pointer to the closest character or NULL if no character is close enough. + */ + class CHARACTER *closest_character(vec2 pos, float radius, ENTITY *notthis); + + /* + Function: insert_entity + Adds an entity to the world. + + Arguments: + entity - Entity to add + */ + void insert_entity(ENTITY *entity); + + /* + Function: remove_entity + Removes an entity from the world. + + Arguments: + entity - Entity to remove + */ + void remove_entity(ENTITY *entity); + + /* + Function: destroy_entity + Destroys an entity in the world. + + Arguments: + entity - Entity to destroy + */ + void destroy_entity(ENTITY *entity); + + /* + Function: snap + Calls snap on all the entities in the world to create + the snapshot. + + Arguments: + snapping_client - ID of the client which snapshot + is being created. + */ + void snap(int snapping_client); + + /* + Function: tick + Calls tick on all the entities in the world to progress + the world to the next tick. + + */ + void tick(); +}; diff --git a/src/game/server/gs_common.hpp b/src/game/server/gs_common.hpp index ce03d728..37bb2638 100644 --- a/src/game/server/gs_common.hpp +++ b/src/game/server/gs_common.hpp @@ -32,590 +32,15 @@ inline bool cmask_is_set(int mask, int cid) { return (mask&cmask_one(cid)) != 0; */ +#include "eventhandler.hpp" -// -class EVENT_HANDLER -{ - static const int MAX_EVENTS = 128; - static const int MAX_DATASIZE = 128*64; - - int types[MAX_EVENTS]; // TODO: remove some of these arrays - int offsets[MAX_EVENTS]; - int sizes[MAX_EVENTS]; - int client_masks[MAX_EVENTS]; - char data[MAX_DATASIZE]; - - int current_offset; - int num_events; -public: - EVENT_HANDLER(); - void *create(int type, int size, int mask = -1); - void clear(); - void snap(int snapping_client); -}; - -/* - Class: Entity - Basic entity class. -*/ -class ENTITY -{ -private: - friend class GAMEWORLD; // thy these? - ENTITY *prev_entity; - ENTITY *next_entity; - - ENTITY *prev_type_entity; - ENTITY *next_type_entity; -protected: - bool marked_for_destroy; - int id; - int objtype; -public: - - ENTITY(int objtype); - virtual ~ENTITY(); - - ENTITY *typenext() { return next_type_entity; } - ENTITY *typeprev() { return prev_type_entity; } - - /* - Function: destroy - Destorys the entity. - */ - virtual void destroy() { delete this; } - - /* - Function: reset - Called when the game resets the map. Puts the entity - back to it's starting state or perhaps destroys it. - */ - virtual void reset() {} - - /* - Function: tick - Called progress the entity to the next tick. Updates - and moves the entity to it's new state and position. - */ - virtual void tick() {} - - /* - Function: tick_defered - Called after all entities tick() function has been called. - */ - virtual void tick_defered() {} - - /* - Function: snap - Called when a new snapshot is being generated for a specific - client. - - Arguments: - snapping_client - ID of the client which snapshot is - being generated. Could be -1 to create a complete - snapshot of everything in the game for demo - recording. - */ - virtual void snap(int snapping_client) {} - - /* - Variable: proximity_radius - Contains the physical size of the entity. - */ - float proximity_radius; - - /* - Variable: pos - Contains the current posititon of the entity. - */ - vec2 pos; -}; +#include "entity.hpp" -/* - Class: Game World - Tracks all entities in the game. Propagates tick and - snap calls to all entities. -*/ -class GAMEWORLD -{ - void reset(); - void remove_entities(); - - enum - { - NUM_ENT_TYPES=10, // TODO: are more exact value perhaps? :) - }; - - // TODO: two lists seams kinda not good, shouldn't be needed - ENTITY *first_entity; - ENTITY *first_entity_types[NUM_ENT_TYPES]; - -public: - bool reset_requested; - bool paused; - WORLD_CORE core; - - GAMEWORLD(); - ~GAMEWORLD(); - - ENTITY *find_first() { return first_entity; } - ENTITY *find_first(int type); - - /* - Function: find_entities - Finds entities close to a position and returns them in a list. - - Arguments: - pos - Position. - radius - How close the entities have to be. - ents - Pointer to a list that should be filled with the pointers - to the entities. - max - Number of entities that fits into the ents array. - type - Type of the entities to find. -1 for all types. - - Returns: - Number of entities found and added to the ents array. - */ - int find_entities(vec2 pos, float radius, ENTITY **ents, int max, int type = -1); - - /* - Function: interserct_character - Finds the closest character that intersects the line. - - Arguments: - pos0 - Start position - pos2 - End position - radius - How for from the line the character is allowed to be. - new_pos - Intersection position - notthis - Entity to ignore intersecting with - - Returns: - Returns a pointer to the closest hit or NULL of there is no intersection. - */ - class CHARACTER *intersect_character(vec2 pos0, vec2 pos1, float radius, vec2 &new_pos, class ENTITY *notthis = 0); - - /* - Function: closest_character - Finds the closest character to a specific point. - - Arguments: - pos - The center position. - radius - How far off the character is allowed to be - notthis - Entity to ignore - - Returns: - Returns a pointer to the closest character or NULL if no character is close enough. - */ - class CHARACTER *closest_character(vec2 pos, float radius, ENTITY *notthis); - - /* - Function: insert_entity - Adds an entity to the world. - - Arguments: - entity - Entity to add - */ - void insert_entity(ENTITY *entity); - - /* - Function: remove_entity - Removes an entity from the world. - - Arguments: - entity - Entity to remove - */ - void remove_entity(ENTITY *entity); - - /* - Function: destroy_entity - Destroys an entity in the world. - - Arguments: - entity - Entity to destroy - */ - void destroy_entity(ENTITY *entity); - - /* - Function: snap - Calls snap on all the entities in the world to create - the snapshot. - - Arguments: - snapping_client - ID of the client which snapshot - is being created. - */ - void snap(int snapping_client); - - /* - Function: tick - Calls tick on all the entities in the world to progress - the world to the next tick. - - */ - void tick(); -}; - -/* - Class: Game Controller - Controls the main game logic. Keeping track of team and player score, - winning conditions and specific game logic. -*/ -class GAMECONTROLLER -{ -protected: - void cyclemap(); - void resetgame(); - - int round_start_tick; - int game_over_tick; - int sudden_death; - - int teamscore[2]; - - int warmup; - int round_count; - - bool is_teamplay; - -public: - int gametype; - GAMECONTROLLER(); - - void do_team_score_wincheck(); - void do_player_score_wincheck(); - - void do_warmup(int seconds); - - void startround(); - void endround(); - - bool is_friendly_fire(int cid1, int cid2); - - /* - - */ - virtual void tick(); - - virtual void snap(int snapping_client); - - /* - Function: on_entity - Called when the map is loaded to process an entity - in the map. - - Arguments: - index - Entity index. - pos - Where the entity is located in the world. - - Returns: - bool? - */ - virtual bool on_entity(int index, vec2 pos); - - /* - Function: on_character_spawn - Called when a character spawns into the game world. - - Arguments: - chr - The character that was spawned. - */ - virtual void on_character_spawn(class CHARACTER *chr) {} - - /* - Function: on_character_death - Called when a character in the world dies. - - Arguments: - victim - The character that died. - killer - The player that killed it. - weapon - What weapon that killed it. Can be -1 for undefined - weapon when switching team or player suicides. - */ - virtual int on_character_death(class CHARACTER *victim, class PLAYER *killer, int weapon); - - virtual void on_player_info_change(class PLAYER *p); - - /* - - */ - virtual const char *get_team_name(int team); - virtual int get_auto_team(int notthisid); - virtual bool can_join_team(int team, int notthisid); - int clampteam(int team); - - virtual void post_reset(); -}; - -// TODO: move to seperate file -class PICKUP : public ENTITY -{ -public: - static const int phys_size = 14; - - int type; - int subtype; // weapon type for instance? - int spawntick; - PICKUP(int _type, int _subtype = 0); - - virtual void reset(); - virtual void tick(); - virtual void snap(int snapping_client); -}; - -// projectile entity -class PROJECTILE : public ENTITY -{ -public: - enum - { - PROJECTILE_FLAGS_EXPLODE = 1 << 0, - }; - - vec2 direction; - ENTITY *powner; // this is nasty, could be removed when client quits - int lifespan; - int owner; - int type; - int flags; - int damage; - int sound_impact; - int weapon; - int bounce; - float force; - int start_tick; - - PROJECTILE(int type, int owner, vec2 pos, vec2 vel, int span, ENTITY* powner, - int damage, int flags, float force, int sound_impact, int weapon); - - vec2 get_pos(float time); - void fill_info(NETOBJ_PROJECTILE *proj); - - virtual void reset(); - virtual void tick(); - virtual void snap(int snapping_client); -}; - -class LASER : public ENTITY -{ - vec2 from; - vec2 dir; - float energy; - int bounces; - int eval_tick; - CHARACTER *owner; - - bool hit_character(vec2 from, vec2 to); - void do_bounce(); - -public: - - LASER(vec2 pos, vec2 direction, float start_energy, CHARACTER *owner); - - virtual void reset(); - virtual void tick(); - virtual void snap(int snapping_client); -}; - - -class CHARACTER : public ENTITY -{ -public: - // player controlling this character - class PLAYER *player; - - bool alive; - - // weapon info - ENTITY *hitobjects[10]; - int numobjectshit; - struct WEAPONSTAT - { - int ammoregenstart; - int ammo; - int ammocost; - bool got; - } weapons[NUM_WEAPONS]; - - int active_weapon; - int last_weapon; - int queued_weapon; - - int reload_timer; - int attack_tick; - - int damage_taken; - - int emote_type; - int emote_stop; - - // TODO: clean this up - char skin_name[64]; - int use_custom_color; - int color_body; - int color_feet; - - int last_action; // last tick that the player took any action ie some input - - // these are non-heldback inputs - NETOBJ_PLAYER_INPUT latest_previnput; - NETOBJ_PLAYER_INPUT latest_input; - - // input - NETOBJ_PLAYER_INPUT previnput; - NETOBJ_PLAYER_INPUT input; - int num_inputs; - int jumped; - - int damage_taken_tick; - - int health; - int armor; - - // ninja - struct - { - vec2 activationdir; - int activationtick; - int currentcooldown; - int currentmovetime; - } ninja; - - // - //int score; - int team; - int player_state; // if the client is chatting, accessing a menu or so - - // the player core for the physics - CHARACTER_CORE core; - - // - CHARACTER(); - - virtual void reset(); - virtual void destroy(); - - bool is_grounded(); - - void set_weapon(int w); - - void handle_weaponswitch(); - void do_weaponswitch(); - - int handle_weapons(); - int handle_ninja(); - - void on_predicted_input(NETOBJ_PLAYER_INPUT *new_input); - void on_direct_input(NETOBJ_PLAYER_INPUT *new_input); - void fire_weapon(); - - void die(int killer, int weapon); - - bool take_damage(vec2 force, int dmg, int from, int weapon); - - - bool spawn(PLAYER *player, vec2 pos, int team); - //bool init_tryspawn(int team); - bool remove(); - - static const int phys_size = 28; - - virtual void tick(); - virtual void tick_defered(); - virtual void snap(int snaping_client); - - bool increase_health(int amount); - bool increase_armor(int amount); -}; - -// player object -class PLAYER -{ -public: - PLAYER(); - - // TODO: clean this up - char skin_name[64]; - int use_custom_color; - int color_body; - int color_feet; - - // - bool spawning; - int client_id; - int team; - int score; - - // - int64 last_chat; - - // network latency calculations - struct - { - int accum; - int accum_min; - int accum_max; - int avg; - int min; - int max; - } latency; - - CHARACTER character; - - // this is used for snapping so we know how we can clip the view for the player - vec2 view_pos; - - void init(int client_id); - - CHARACTER *get_character(); - - void kill_character(); - - void try_respawn(); - void respawn(); - void set_team(int team); - - void tick(); - void snap(int snaping_client); - - void on_direct_input(NETOBJ_PLAYER_INPUT *new_input); - void on_predicted_input(NETOBJ_PLAYER_INPUT *new_input); - void on_disconnect(); -}; - -class GAMECONTEXT -{ -public: - GAMECONTEXT(); - void clear(); - - EVENT_HANDLER events; - PLAYER players[MAX_CLIENTS]; - - GAMECONTROLLER *controller; - GAMEWORLD world; - - void tick(); - void snap(int client_id); - - // helper functions - 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_playerspawn(vec2 p); - void create_death(vec2 p, int who); - void create_sound(vec2 pos, int sound, int mask=-1); - void create_sound_global(int sound, int target=-1); - - // network - void send_chat(int cid, int team, const char *text); - void send_emoticon(int cid, int emoticon); - void send_weapon_pickup(int cid, int weapon); - void send_broadcast(const char *text, int cid); - void send_info(int who, int to_who); -}; - -extern GAMECONTEXT game; +#include "gamecontroller.hpp" +#include "entities/character.hpp" +#include "player.hpp" +#include "gamecontext.hpp" enum { diff --git a/src/game/server/gs_ent_pickup.cpp b/src/game/server/gs_ent_pickup.cpp deleted file mode 100644 index 304596d0..00000000 --- a/src/game/server/gs_ent_pickup.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include -#include "gs_common.hpp" - -////////////////////////////////////////////////// -// powerup -////////////////////////////////////////////////// -PICKUP::PICKUP(int _type, int _subtype) -: ENTITY(NETOBJTYPE_PICKUP) -{ - type = _type; - subtype = _subtype; - proximity_radius = phys_size; - - reset(); - - // TODO: should this be done here? - game.world.insert_entity(this); -} - -void PICKUP::reset() -{ - if (data->pickups[type].spawndelay > 0) - spawntick = server_tick() + server_tickspeed() * data->pickups[type].spawndelay; - else - spawntick = -1; -} - -void PICKUP::tick() -{ - // wait for respawn - if(spawntick > 0) - { - if(server_tick() > spawntick) - { - // respawn - spawntick = -1; - - if(type == POWERUP_WEAPON) - game.create_sound(pos, SOUND_WEAPON_SPAWN); - } - else - return; - } - // Check if a player intersected us - CHARACTER *chr = game.world.closest_character(pos, 20.0f, 0); - if(chr) - { - // player picked us up, is someone was hooking us, let them go - int respawntime = -1; - switch (type) - { - case POWERUP_HEALTH: - if(chr->increase_health(1)) - { - game.create_sound(pos, SOUND_PICKUP_HEALTH); - respawntime = data->pickups[type].respawntime; - } - break; - case POWERUP_ARMOR: - if(chr->increase_armor(1)) - { - game.create_sound(pos, SOUND_PICKUP_ARMOR); - respawntime = data->pickups[type].respawntime; - } - break; - - case POWERUP_WEAPON: - if(subtype >= 0 && subtype < NUM_WEAPONS) - { - if(chr->weapons[subtype].ammo < data->weapons.id[subtype].maxammo || !chr->weapons[subtype].got) - { - chr->weapons[subtype].got = true; - chr->weapons[subtype].ammo = min(data->weapons.id[subtype].maxammo, chr->weapons[subtype].ammo + 10); - respawntime = data->pickups[type].respawntime; - - // TODO: data compiler should take care of stuff like this - if(subtype == WEAPON_GRENADE) - game.create_sound(pos, SOUND_PICKUP_GRENADE); - else if(subtype == WEAPON_SHOTGUN) - game.create_sound(pos, SOUND_PICKUP_SHOTGUN); - else if(subtype == WEAPON_RIFLE) - game.create_sound(pos, SOUND_PICKUP_SHOTGUN); - - if(chr->player) - game.send_weapon_pickup(chr->player->client_id, subtype); - } - } - break; - case POWERUP_NINJA: - { - // activate ninja on target player - chr->ninja.activationtick = server_tick(); - chr->weapons[WEAPON_NINJA].got = true; - chr->last_weapon = chr->active_weapon; - chr->active_weapon = WEAPON_NINJA; - respawntime = data->pickups[type].respawntime; - game.create_sound(pos, SOUND_PICKUP_NINJA); - - // loop through all players, setting their emotes - ENTITY *ents[64]; - int num = game.world.find_entities(vec2(0, 0), 1000000, ents, 64, NETOBJTYPE_CHARACTER); - for (int i = 0; i < num; i++) - { - CHARACTER *c = (CHARACTER *)ents[i]; - if (c != chr) - { - c->emote_type = EMOTE_SURPRISE; - c->emote_stop = server_tick() + server_tickspeed(); - } - } - - chr->emote_type = EMOTE_ANGRY; - chr->emote_stop = server_tick() + 1200 * server_tickspeed() / 1000; - - break; - } - default: - break; - }; - - if(respawntime >= 0) - { - dbg_msg("game", "pickup player='%d:%s' item=%d/%d", - chr->player->client_id, server_clientname(chr->player->client_id), type, subtype); - spawntick = server_tick() + server_tickspeed() * respawntime; - } - } -} - -void PICKUP::snap(int snapping_client) -{ - if(spawntick != -1) - return; - - NETOBJ_PICKUP *up = (NETOBJ_PICKUP *)snap_new_item(NETOBJTYPE_PICKUP, id, sizeof(NETOBJ_PICKUP)); - up->x = (int)pos.x; - up->y = (int)pos.y; - up->type = type; // TODO: two diffrent types? what gives? - up->subtype = subtype; -} diff --git a/src/game/server/gs_ent_player.cpp b/src/game/server/gs_ent_player.cpp deleted file mode 100644 index b476a972..00000000 --- a/src/game/server/gs_ent_player.cpp +++ /dev/null @@ -1,1128 +0,0 @@ -#include -#include -#include -#include "gs_common.hpp" - - -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; -} - -// player -CHARACTER::CHARACTER() -: ENTITY(NETOBJTYPE_CHARACTER) -{} - -void CHARACTER::reset() -{ - -} - -bool CHARACTER::spawn(PLAYER *player, vec2 pos, int team) -{ - player_state = PLAYERSTATE_UNKNOWN; - emote_stop = -1; - last_action = -1; - active_weapon = WEAPON_GUN; - last_weapon = WEAPON_HAMMER; - queued_weapon = -1; - - //clear(); - this->player = player; - this->pos = pos; - this->team = team; - - core.reset(); - core.world = &game.world.core; - core.pos = pos; - game.world.core.characters[player->client_id] = &core; - - game.world.insert_entity(this); - alive = true; - return true; -} - -void CHARACTER::destroy() -{ - game.world.core.characters[player->client_id] = 0; - alive = false; -} - -void CHARACTER::set_weapon(int w) -{ - if(w == active_weapon) - return; - - last_weapon = active_weapon; - queued_weapon = -1; - active_weapon = w; - if(active_weapon < 0 || active_weapon >= NUM_WEAPONS) - active_weapon = 0; - - game.create_sound(pos, SOUND_WEAPON_SWITCH); -} - -bool CHARACTER::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; -} - - -int CHARACTER::handle_ninja() -{ - vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y)); - - if ((server_tick() - ninja.activationtick) > (data->weapons.ninja.duration * server_tickspeed() / 1000)) - { - // time's up, return - weapons[WEAPON_NINJA].got = false; - active_weapon = last_weapon; - if(active_weapon == WEAPON_NINJA) - active_weapon = WEAPON_GUN; - set_weapon(active_weapon); - return 0; - } - - // force ninja weapon - set_weapon(WEAPON_NINJA); - - // Check if it should activate - if (count_input(latest_previnput.fire, latest_input.fire).presses && (server_tick() > ninja.currentcooldown)) - { - // ok then, activate ninja - attack_tick = server_tick(); - ninja.activationdir = direction; - ninja.currentmovetime = data->weapons.ninja.movetime * server_tickspeed() / 1000; - ninja.currentcooldown = data->weapons.ninja.base->firedelay * server_tickspeed() / 1000 + server_tick(); - - // reset hit objects - numobjectshit = 0; - - game.create_sound(pos, SOUND_NINJA_FIRE); - - // release all hooks when ninja is activated - //release_hooked(); - //release_hooks(); - } - - ninja.currentmovetime--; - - if (ninja.currentmovetime == 0) - { - // reset player velocity - core.vel *= 0.2f; - //return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; - } - - if (ninja.currentmovetime > 0) - { - // Set player velocity - core.vel = ninja.activationdir * data->weapons.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 ((ninja.currentmovetime % 2) == 0) - { - //create_smoke(pos); - } - - // check if we hit anything along the way - { - CHARACTER *ents[64]; - vec2 dir = pos - oldpos; - float radius = phys_size * 2.0f; //length(dir * 0.5f); - vec2 center = oldpos + dir * 0.5f; - int num = game.world.find_entities(center, radius, (ENTITY**)ents, 64, NETOBJTYPE_CHARACTER); - - 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... - game.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.ninja.base->damage, player->client_id,WEAPON_NINJA); - } - } - return 0; - } - - return 0; -} - - -void CHARACTER::do_weaponswitch() -{ - if(reload_timer != 0) // make sure we have reloaded - return; - - if(queued_weapon == -1) // check for a queued weapon - return; - - if(weapons[WEAPON_NINJA].got) // if we have ninja, no weapon selection is possible - return; - - // switch weapon - set_weapon(queued_weapon); -} - -void CHARACTER::handle_weaponswitch() -{ - int wanted_weapon = active_weapon; - if(queued_weapon != -1) - wanted_weapon = queued_weapon; - - // select weapon - int next = count_input(latest_previnput.next_weapon, latest_input.next_weapon).presses; - int prev = count_input(latest_previnput.prev_weapon, latest_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--; - } - } - - // direct weapon selection - if(latest_input.wanted_weapon) - wanted_weapon = input.wanted_weapon-1; - - // check for insane values - if(wanted_weapon >= 0 && wanted_weapon < NUM_WEAPONS && wanted_weapon != active_weapon && weapons[wanted_weapon].got) - queued_weapon = wanted_weapon; - - do_weaponswitch(); -} - -void CHARACTER::fire_weapon() -{ - if(reload_timer != 0 || active_weapon == WEAPON_NINJA) - return; - - do_weaponswitch(); - - vec2 direction = normalize(vec2(latest_input.target_x, latest_input.target_y)); - - bool fullauto = false; - if(active_weapon == WEAPON_GRENADE || active_weapon == WEAPON_SHOTGUN || active_weapon == WEAPON_RIFLE) - fullauto = true; - - - // check if we gonna fire - bool will_fire = false; - if(count_input(latest_previnput.fire, latest_input.fire).presses) will_fire = true; - if(fullauto && (latest_input.fire&1) && weapons[active_weapon].ammo) will_fire = true; - if(!will_fire) - return; - - // check for ammo - if(!weapons[active_weapon].ammo) - { - game.create_sound(pos, SOUND_WEAPON_NOAMMO); - return; - } - - vec2 projectile_startpos = pos+direction*phys_size*0.75f; - - switch(active_weapon) - { - case WEAPON_HAMMER: - { - // reset objects hit - numobjectshit = 0; - game.create_sound(pos, SOUND_HAMMER_FIRE); - - CHARACTER *ents[64]; - int num = game.world.find_entities(pos+direction*phys_size*0.75f, phys_size*0.5f, (ENTITY**)ents, 64, NETOBJTYPE_CHARACTER); - - for (int i = 0; i < num; i++) - { - CHARACTER *target = ents[i]; - if (target == this) - continue; - - // hit a player, give him damage and stuffs... - vec2 fdir = normalize(ents[i]->pos - pos); - - // set his velocity to fast upward (for now) - game.create_sound(pos, SOUND_HAMMER_HIT); - ents[i]->take_damage(vec2(0,-1.0f), data->weapons.hammer.base->damage, player->client_id, active_weapon); - 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; - } - - } break; - - case WEAPON_GUN: - { - PROJECTILE *proj = new PROJECTILE(WEAPON_GUN, - player->client_id, - projectile_startpos, - direction, - (int)(server_tickspeed()*tuning.gun_lifetime), - this, - 1, 0, 0, -1, WEAPON_GUN); - - // pack the projectile and send it to the client directly - NETOBJ_PROJECTILE p; - proj->fill_info(&p); - - msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0); - msg_pack_int(1); - for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++) - msg_pack_int(((int *)&p)[i]); - msg_pack_end(); - server_send_msg(player->client_id); - - game.create_sound(pos, SOUND_GUN_FIRE); - } break; - - case WEAPON_SHOTGUN: - { - int shotspread = 2; - - msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0); - msg_pack_int(shotspread*2+1); - - for(int i = -shotspread; i <= shotspread; i++) - { - float spreading[] = {-0.185f, -0.070f, 0, 0.070f, 0.185f}; - float a = get_angle(direction); - a += spreading[i+2]; - float v = 1-(abs(i)/(float)shotspread); - float speed = mix((float)tuning.shotgun_speeddiff, 1.0f, v); - PROJECTILE *proj = new PROJECTILE(WEAPON_SHOTGUN, - player->client_id, - projectile_startpos, - vec2(cosf(a), sinf(a))*speed, - (int)(server_tickspeed()*tuning.shotgun_lifetime), - this, - 1, 0, 0, -1, WEAPON_SHOTGUN); - - // pack the projectile and send it to the client directly - NETOBJ_PROJECTILE p; - proj->fill_info(&p); - - for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++) - msg_pack_int(((int *)&p)[i]); - } - - msg_pack_end(); - server_send_msg(player->client_id); - - game.create_sound(pos, SOUND_SHOTGUN_FIRE); - } break; - - case WEAPON_GRENADE: - { - PROJECTILE *proj = new PROJECTILE(WEAPON_GRENADE, - player->client_id, - projectile_startpos, - direction, - (int)(server_tickspeed()*tuning.grenade_lifetime), - this, - 1, PROJECTILE::PROJECTILE_FLAGS_EXPLODE, 0, SOUND_GRENADE_EXPLODE, WEAPON_GRENADE); - - // pack the projectile and send it to the client directly - NETOBJ_PROJECTILE p; - proj->fill_info(&p); - - msg_pack_start(NETMSGTYPE_SV_EXTRAPROJECTILE, 0); - msg_pack_int(1); - for(unsigned i = 0; i < sizeof(NETOBJ_PROJECTILE)/sizeof(int); i++) - msg_pack_int(((int *)&p)[i]); - msg_pack_end(); - server_send_msg(player->client_id); - - game.create_sound(pos, SOUND_GRENADE_FIRE); - } break; - - case WEAPON_RIFLE: - { - new LASER(pos, direction, tuning.laser_reach, this); - game.create_sound(pos, SOUND_RIFLE_FIRE); - } break; - - } - - if(weapons[active_weapon].ammo > 0) // -1 == unlimited - weapons[active_weapon].ammo--; - attack_tick = server_tick(); - reload_timer = data->weapons.id[active_weapon].firedelay * server_tickspeed() / 1000; -} - -int CHARACTER::handle_weapons() -{ - vec2 direction = normalize(vec2(latest_input.target_x, latest_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(); - } - - // fire weapon, if wanted - fire_weapon(); - - // ammo regen - int ammoregentime = data->weapons.id[active_weapon].ammoregentime; - if(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) >= ammoregentime * server_tickspeed() / 1000) - { - // Add some ammo - weapons[active_weapon].ammo = min(weapons[active_weapon].ammo + 1, 10); - weapons[active_weapon].ammoregenstart = -1; - } - } - else - { - weapons[active_weapon].ammoregenstart = -1; - } - } - - return 0; -} - -void CHARACTER::on_predicted_input(NETOBJ_PLAYER_INPUT *new_input) -{ - // check for changes - if(mem_comp(&input, new_input, sizeof(NETOBJ_PLAYER_INPUT)) != 0) - last_action = server_tick(); - - // copy new input - mem_copy(&input, new_input, sizeof(input)); - num_inputs++; - - // or are not allowed to aim in the center - if(input.target_x == 0 && input.target_y == 0) - input.target_y = -1; -} - -void CHARACTER::on_direct_input(NETOBJ_PLAYER_INPUT *new_input) -{ - mem_copy(&latest_previnput, &latest_input, sizeof(latest_input)); - mem_copy(&latest_input, new_input, sizeof(latest_input)); - - if(num_inputs > 2 && team != -1) - { - handle_weaponswitch(); - fire_weapon(); - } -} - -void CHARACTER::tick() -{ - //input = latest_input; - - // grab latest input - /* - { - int size = 0; - int *input = server_latestinput(client_id, &size); - if(input) - { - mem_copy(&latest_previnput, &latest_input, sizeof(latest_input)); - mem_copy(&latest_input, input, sizeof(latest_input)); - } - }*/ - - // check if we have enough input - // this is to prevent initial weird clicks - /* - if(num_inputs < 2) - { - latest_previnput = latest_input; - previnput = input; - }*/ - - //game.world.core.players[player->client_id] = &core; - - // enable / disable physics - /* - if(team == -1 || dead) - { - game.world.core.players[client_id] = 0; - //game.world.remove_entity(this); - } - else - { - game.world.core.players[client_id] = &core; - //game.world._entity(this); - } - - // 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()/2 && count_input(latest_previnput.fire, latest_input.fire).presses) - die_tick = -1; - 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(); - - player_state = input.player_state; - - // Previnput - previnput = input; - return; -} - -void CHARACTER::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(player->client_id); - - if(events&COREEVENT_GROUND_JUMP) game.create_sound(pos, SOUND_PLAYER_JUMP, mask); - if(events&COREEVENT_AIR_JUMP) - { - game.create_sound(pos, SOUND_PLAYER_AIRJUMP, mask); - NETEVENT_COMMON *c = (NETEVENT_COMMON *)game.events.create(NETEVENTTYPE_AIRJUMP, sizeof(NETEVENT_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) game.create_sound(pos, SOUND_HOOK_ATTACH_PLAYER, cmask_all()); - if(events&COREEVENT_HOOK_ATTACH_GROUND) game.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; - } -} - -bool CHARACTER::increase_health(int amount) -{ - if(health >= 10) - return false; - health = clamp(health+amount, 0, 10); - return true; -} - -bool CHARACTER::increase_armor(int amount) -{ - if(armor >= 10) - return false; - armor = clamp(armor+amount, 0, 10); - return true; -} - -void CHARACTER::die(int killer, int weapon) -{ - /*if (dead || team == -1) - return;*/ - int mode_special = game.controller->on_character_death(this, &game.players[killer], weapon); - - dbg_msg("game", "kill killer='%d:%s' victim='%d:%s' weapon=%d special=%d", - killer, server_clientname(killer), - player->client_id, server_clientname(player->client_id), weapon, mode_special); - - // send the kill message - NETMSG_SV_KILLMSG msg; - msg.killer = killer; - msg.victim = player->client_id; - msg.weapon = weapon; - msg.mode_special = mode_special; - msg.pack(MSGFLAG_VITAL); - server_send_msg(-1); - - // a nice sound - game.create_sound(pos, SOUND_PLAYER_DIE); - - // set dead state - // TODO: do stuff here - /* - die_pos = pos; - dead = true; - die_tick = server_tick(); - */ - alive = false; - game.world.remove_entity(this); - game.create_death(pos, player->client_id); -} - -bool CHARACTER::take_damage(vec2 force, int dmg, int from, int weapon) -{ - core.vel += force; - - if(game.controller->is_friendly_fire(player->client_id, from) && !config.sv_teamdamage) - return false; - - // player only inflicts half damage on self - if(from == player->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 - game.create_damageind(pos, damage_taken*0.25f, dmg); - } - else - { - damage_taken = 0; - game.create_damageind(pos, 0, dmg); - } - - if(dmg) - { - if(armor) - { - if(dmg > 1) - { - health--; - dmg--; - } - - if(dmg > armor) - { - dmg -= armor; - armor = 0; - } - else - { - armor -= dmg; - dmg = 0; - } - } - - health -= dmg; - } - - damage_taken_tick = server_tick(); - - // do damage hit sound - if(from >= 0 && from != player->client_id) - game.create_sound(game.players[from].view_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 != player->client_id) - { - CHARACTER *chr = &game.players[from].character; - chr->emote_type = EMOTE_HAPPY; - chr->emote_stop = server_tick() + server_tickspeed(); - } - - return false; - } - - if (dmg > 2) - game.create_sound(pos, SOUND_PLAYER_PAIN_LONG); - else - game.create_sound(pos, SOUND_PLAYER_PAIN_SHORT); - - emote_type = EMOTE_PAIN; - emote_stop = server_tick() + 500 * server_tickspeed() / 1000; - - // spawn blood? - return true; -} - -void CHARACTER::snap(int snaping_client) -{ - if(distance(game.players[snaping_client].view_pos, pos) > 1000.0f) - return; - - NETOBJ_CHARACTER *character = (NETOBJ_CHARACTER *)snap_new_item(NETOBJTYPE_CHARACTER, player->client_id, sizeof(NETOBJ_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 != player->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 = 0; - character->health = 0; - character->armor = 0; - - character->weapon = active_weapon; - character->attacktick = attack_tick; - - character->wanted_direction = input.direction; - /* - if(input.left && !input.right) - character->wanted_direction = -1; - else if(!input.left && input.right) - character->wanted_direction = 1;*/ - - - if(player->client_id == snaping_client) - { - character->health = health; - character->armor = armor; - if(weapons[active_weapon].ammo > 0) - character->ammocount = weapons[active_weapon].ammo; - } - - if (character->emote == EMOTE_NORMAL) - { - if(250 - ((server_tick() - last_action)%(250)) < 5) - character->emote = EMOTE_BLINK; - } - - character->player_state = player_state; -} - -PLAYER::PLAYER() -{ -} - -void PLAYER::init(int client_id) -{ - // clear everything - mem_zero(this, sizeof(*this)); - new(this) PLAYER(); - this->client_id = client_id; -} - -void PLAYER::tick() -{ - server_setclientscore(client_id, score); - - // 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; - } - } - - if(spawning && !get_character()) - try_respawn(); - - if(get_character()) - view_pos = get_character()->pos; -} - -void PLAYER::snap(int snaping_client) -{ - NETOBJ_PLAYER_INFO *info = (NETOBJ_PLAYER_INFO *)snap_new_item(NETOBJTYPE_PLAYER_INFO, client_id, sizeof(NETOBJ_PLAYER_INFO)); - - info->latency = latency.min; - info->latency_flux = latency.max-latency.min; - info->local = 0; - info->cid = client_id; - info->score = score; - info->team = team; - - if(client_id == snaping_client) - info->local = 1; -} - -void PLAYER::on_disconnect() -{ - kill_character(); - - //game.controller->on_player_death(&game.players[client_id], 0, -1); - - char buf[512]; - str_format(buf, sizeof(buf), "%s has left the game", server_clientname(client_id)); - game.send_chat(-1, CHAT_ALL, buf); - - dbg_msg("game", "leave player='%d:%s'", client_id, server_clientname(client_id)); - - // clear this whole structure - init(-1); - - /*game.world.remove_entity(&game.players[client_id]); - game.world.core.players[client_id] = 0x0; - game.players[client_id].client_id = -1; */ -} - -void PLAYER::on_predicted_input(NETOBJ_PLAYER_INPUT *new_input) -{ - CHARACTER *chr = get_character(); - if(chr) - chr->on_predicted_input(new_input); -} - -void PLAYER::on_direct_input(NETOBJ_PLAYER_INPUT *new_input) -{ - CHARACTER *chr = get_character(); - if(chr) - chr->on_direct_input(new_input); - - if(!chr && team >= 0 && (new_input->fire&1)) - { - spawning = true; - dbg_msg("", "I wanna spawn"); - } -} - -CHARACTER *PLAYER::get_character() -{ - if(character.alive) - return &character; - return 0; -} - -void PLAYER::kill_character() -{ - CHARACTER *chr = get_character(); - if(chr) - chr->die(-1, -1); -} - -void PLAYER::respawn() -{ - spawning = true; -} - -void PLAYER::set_team(int new_team) -{ - // clamp the team - new_team = game.controller->clampteam(new_team); - if(team == new_team) - return; - - char buf[512]; - str_format(buf, sizeof(buf), "%s joined the %s", server_clientname(client_id), game.controller->get_team_name(new_team)); - game.send_chat(-1, CHAT_ALL, buf); - - kill_character(); - team = new_team; - score = 0; - dbg_msg("game", "team_join player='%d:%s' team=%d", client_id, server_clientname(client_id), team); - - game.controller->on_player_info_change(&game.players[client_id]); - - // send all info to this client - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(game.players[i].client_id != -1) - game.send_info(i, -1); - } -} - -vec2 spawn_points[3][64]; -int num_spawn_points[3] = {0}; - -struct SPAWNEVAL -{ - SPAWNEVAL() - { - got = false; - friendly_team = -1; -// die_pos = vec2(0,0); - pos = vec2(100,100); - } - - vec2 pos; - bool got; - int friendly_team; - float score; -// vec2 die_pos; -}; - -static float evaluate_spawn(SPAWNEVAL *eval, vec2 pos) -{ - float score = 0.0f; - CHARACTER *c = (CHARACTER *)game.world.find_first(NETOBJTYPE_CHARACTER); - for(; c; c = (CHARACTER *)c->typenext()) - { - // team mates are not as dangerous as enemies - float scoremod = 1.0f; - if(eval->friendly_team != -1 && c->team == eval->friendly_team) - scoremod = 0.5f; - - float d = distance(pos, c->pos); - if(d == 0) - score += 1000000000.0f; - else - score += 1.0f/d; - } - - // weight in the die posititon - /* - float d = distance(pos, eval->die_pos); - if(d == 0) - score += 1000000000.0f; - else - score += 1.0f/d;*/ - - return score; -} - -static void evaluate_spawn_type(SPAWNEVAL *eval, int t) -{ - // get spawn point - /* - int start, num; - map_get_type(t, &start, &num); - if(!num) - return; - */ - for(int i = 0; i < num_spawn_points[t]; i++) - { - //num_spawn_points[t] - //mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + i, NULL, NULL); - vec2 p = spawn_points[t][i];// vec2((float)sp->x, (float)sp->y); - float s = evaluate_spawn(eval, p); - if(!eval->got || eval->score > s) - { - eval->got = true; - eval->score = s; - eval->pos = p; - } - } -} - -void PLAYER::try_respawn() -{ - vec2 spawnpos = vec2(100.0f, -60.0f); - - // get spawn point - SPAWNEVAL eval; - //eval.die_pos = die_pos; - - eval.pos = vec2(100, 100); - - if(game.controller->gametype == GAMETYPE_CTF) - { - eval.friendly_team = team; - - // try first try own team spawn, then normal spawn and then enemy - evaluate_spawn_type(&eval, 1+(team&1)); - if(!eval.got) - { - evaluate_spawn_type(&eval, 0); - if(!eval.got) - evaluate_spawn_type(&eval, 1+((team+1)&1)); - } - } - else - { - if(game.controller->gametype == GAMETYPE_TDM) - eval.friendly_team = team; - - evaluate_spawn_type(&eval, 0); - evaluate_spawn_type(&eval, 1); - evaluate_spawn_type(&eval, 2); - } - - spawnpos = eval.pos; - - // check if the position is occupado - ENTITY *ents[2] = {0}; - int num_ents = game.world.find_entities(spawnpos, 64, ents, 2, NETOBJTYPE_CHARACTER); - - if(num_ents == 0) - { - spawning = false; - character.spawn(this, spawnpos, team); - } - - /* - pos = spawnpos; - - core.pos = pos; - core.vel = vec2(0,0); - core.hooked_player = -1; - - health = 10; - armor = 0; - jumped = 0; - - mem_zero(&ninja, sizeof(ninja)); - - dead = false; - player_state = PLAYERSTATE_PLAYING; - - game.world.insert_entity(this); - - 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 = 10; - - active_weapon = WEAPON_GUN; - last_weapon = WEAPON_HAMMER; - queued_weapon = 0; - - reload_timer = 0; - - // Create sound and spawn effects - game.create_sound(pos, SOUND_PLAYER_SPAWN); - game.create_playerspawn(pos); - - game.controller->on_player_spawn(player); - */ -} diff --git a/src/game/server/gs_game.cpp b/src/game/server/gs_game.cpp deleted file mode 100644 index 685d61ad..00000000 --- a/src/game/server/gs_game.cpp +++ /dev/null @@ -1,416 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include -#include -#include "gs_common.hpp" - -GAMECONTROLLER::GAMECONTROLLER() -{ - // 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; -} - -// UGLY!!!! -extern vec2 spawn_points[3][64]; -extern int num_spawn_points[3]; - -bool GAMECONTROLLER::on_entity(int index, vec2 pos) -{ - int type = -1; - int subtype = 0; - - if(index == ENTITY_SPAWN) - spawn_points[0][num_spawn_points[0]++] = pos; - else if(index == ENTITY_SPAWN_RED) - spawn_points[1][num_spawn_points[1]++] = pos; - else if(index == ENTITY_SPAWN_BLUE) - spawn_points[2][num_spawn_points[2]++] = pos; - else if(index == ENTITY_ARMOR_1) - type = POWERUP_ARMOR; - else if(index == ENTITY_HEALTH_1) - type = POWERUP_HEALTH; - else if(index == ENTITY_WEAPON_SHOTGUN) - { - type = POWERUP_WEAPON; - subtype = WEAPON_SHOTGUN; - } - else if(index == ENTITY_WEAPON_GRENADE) - { - type = POWERUP_WEAPON; - subtype = WEAPON_GRENADE; - } - else if(index == ENTITY_WEAPON_RIFLE) - { - type = POWERUP_WEAPON; - subtype = WEAPON_RIFLE; - } - else if(index == ENTITY_POWERUP_NINJA && config.sv_powerups) - { - type = POWERUP_NINJA; - subtype = WEAPON_NINJA; - } - - if(type != -1) - { - PICKUP *pickup = new PICKUP(type, subtype); - pickup->pos = pos; - return true; - } - - return false; -} - -void GAMECONTROLLER::endround() -{ - if(warmup) // game can't end when we are running warmup - return; - - game.world.paused = true; - game_over_tick = server_tick(); - sudden_death = 0; -} - -void GAMECONTROLLER::resetgame() -{ - game.world.reset_requested = true; -} - -const char *GAMECONTROLLER::get_team_name(int team) -{ - if(is_teamplay) - { - if(team == 0) - return "red team"; - else if(team == 1) - return "blue team"; - } - else - { - if(team == 0) - return "game"; - } - - return "spectators"; -} - -static bool is_separator(char c) { return c == ';' || c == ' ' || c == ',' || c == '\t'; } - -void GAMECONTROLLER::startround() -{ - resetgame(); - - round_start_tick = server_tick(); - sudden_death = 0; - game_over_tick = -1; - game.world.paused = false; - teamscore[0] = 0; - teamscore[1] = 0; - round_count++; -} - -void GAMECONTROLLER::cyclemap() -{ - if(!strlen(config.sv_maprotation)) - return; - - if(round_count < config.sv_rounds_per_map-1) - return; - - // handle maprotation - const char *map_rotation = config.sv_maprotation; - const char *current_map = config.sv_map; - - int current_map_len = strlen(current_map); - const char *next_map = map_rotation; - while(*next_map) - { - int wordlen = 0; - while(next_map[wordlen] && !is_separator(next_map[wordlen])) - wordlen++; - - if(wordlen == current_map_len && strncmp(next_map, current_map, current_map_len) == 0) - { - // map found - next_map += current_map_len; - while(*next_map && is_separator(*next_map)) - next_map++; - - break; - } - - next_map++; - } - - // restart rotation - if(next_map[0] == 0) - next_map = map_rotation; - - // cut out the next map - char buf[512]; - for(int i = 0; i < 512; i++) - { - buf[i] = next_map[i]; - if(is_separator(next_map[i]) || next_map[i] == 0) - { - buf[i] = 0; - break; - } - } - - // skip spaces - int i = 0; - while(is_separator(buf[i])) - i++; - - dbg_msg("game", "rotating map to %s", &buf[i]); - str_copy(config.sv_map, &buf[i], sizeof(config.sv_map)); -} - -void GAMECONTROLLER::post_reset() -{ - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(game.players[i].client_id != -1) - game.players[i].respawn(); - } -} - -void GAMECONTROLLER::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 GAMECONTROLLER::on_character_death(class CHARACTER *victim, class PLAYER *killer, int weapon) -{ - // do scoreing - if(!killer) - return 0; - if(killer == victim->player) - victim->player->score--; // suicide - else - { - if(is_teamplay && victim->team == killer->team) - killer->score--; // teamkill - else - killer->score++; // normal kill - } - return 0; -} - -void GAMECONTROLLER::do_warmup(int seconds) -{ - warmup = seconds*server_tickspeed(); -} - -bool GAMECONTROLLER::is_friendly_fire(int cid1, int cid2) -{ - if(cid1 == cid2) - return false; - - if(is_teamplay) - { - if(game.players[cid1].team == game.players[cid2].team) - return true; - } - - return false; -} - -void GAMECONTROLLER::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(game.players[i].client_id != -1) - prog = max(prog, (game.players[i].score*100)/config.sv_scorelimit); - } - } - } - - if(warmup) - prog = -1; - - server_setbrowseinfo(gametype, prog); -} - -void GAMECONTROLLER::snap(int snapping_client) -{ - NETOBJ_GAME *gameobj = (NETOBJ_GAME *)snap_new_item(NETOBJTYPE_GAME, 0, sizeof(NETOBJ_GAME)); - gameobj->paused = game.world.paused; - gameobj->game_over = game_over_tick==-1?0:1; - gameobj->sudden_death = sudden_death; - - gameobj->score_limit = config.sv_scorelimit; - gameobj->time_limit = config.sv_timelimit; - gameobj->round_start_tick = round_start_tick; - gameobj->gametype = gametype; - - gameobj->warmup = warmup; - - gameobj->teamscore_red = teamscore[0]; - gameobj->teamscore_blue = teamscore[1]; -} - -int GAMECONTROLLER::get_auto_team(int notthisid) -{ - int numplayers[2] = {0,0}; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(game.players[i].client_id != -1 && game.players[i].client_id != notthisid) - { - if(game.players[i].team == 0 || game.players[i].team == 1) - numplayers[game.players[i].team]++; - } - } - - int team = 0; - if(is_teamplay) - team = numplayers[0] > numplayers[1] ? 1 : 0; - - if(can_join_team(team, notthisid)) - return team; - return -1; -} - -bool GAMECONTROLLER::can_join_team(int team, int notthisid) -{ - (void)team; - int numplayers[2] = {0,0}; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(game.players[i].client_id != -1 && game.players[i].client_id != notthisid) - { - if(game.players[i].team >= 0 || game.players[i].team == 1) - numplayers[game.players[i].team]++; - } - } - - return (numplayers[0] + numplayers[1]) < config.sv_max_clients-config.sv_spectator_slots; -} - -void GAMECONTROLLER::do_player_score_wincheck() -{ - if(game_over_tick == -1 && !warmup) - { - // gather some stats - int topscore = 0; - int topscore_count = 0; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(game.players[i].client_id != -1) - { - if(game.players[i].score > topscore) - { - topscore = game.players[i].score; - topscore_count = 1; - } - else if(game.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; - } - } -} - -void GAMECONTROLLER::do_team_score_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; - } - } -} - -int GAMECONTROLLER::clampteam(int team) -{ - if(team < 0) // spectator - return -1; - if(is_teamplay) - return team&1; - return 0; -} - -GAMECONTROLLER *gamecontroller = 0; diff --git a/src/game/server/gs_game_ctf.cpp b/src/game/server/gs_game_ctf.cpp deleted file mode 100644 index 27da121a..00000000 --- a/src/game/server/gs_game_ctf.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include -#include "gs_common.hpp" -#include "gs_game_ctf.hpp" - -GAMECONTROLLER_CTF::GAMECONTROLLER_CTF() -{ - flags[0] = 0; - flags[1] = 0; - is_teamplay = true; -} - -bool GAMECONTROLLER_CTF::on_entity(int index, vec2 pos) -{ - if(GAMECONTROLLER::on_entity(index, pos)) - return true; - - int team = -1; - if(index == ENTITY_FLAGSTAND_RED) team = 0; - if(index == ENTITY_FLAGSTAND_BLUE) team = 1; - if(team == -1) - return false; - - FLAG *f = new FLAG(team); - f->stand_pos = pos; - f->pos = pos; - flags[team] = f; - return true; -} - -int GAMECONTROLLER_CTF::on_character_death(class CHARACTER *victim, class PLAYER *killer, int weaponid) -{ - GAMECONTROLLER::on_character_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_character == &killer->character) - had_flag |= 2; - if(f && f->carrying_character == victim) - { - game.create_sound_global(SOUND_CTF_DROP); - f->drop_tick = server_tick(); - f->carrying_character = 0; - f->vel = vec2(0,0); - - if(killer && killer->team != victim->team) - killer->score++; - - had_flag |= 1; - } - } - - return had_flag; -} - -void GAMECONTROLLER_CTF::tick() -{ - GAMECONTROLLER::tick(); - - do_team_score_wincheck(); - - for(int fi = 0; fi < 2; fi++) - { - FLAG *f = flags[fi]; - - if(!f) - continue; - - // - if(f->carrying_character) - { - // update flag position - f->pos = f->carrying_character->pos; - - if(flags[fi^1] && flags[fi^1]->at_stand) - { - if(distance(f->pos, flags[fi^1]->pos) < 32) - { - // CAPTURE! \o/ - teamscore[fi^1] += 100; - f->carrying_character->player->score += 5; - - dbg_msg("game", "flag_capture player='%d:%s'", - f->carrying_character->player->client_id, - server_clientname(f->carrying_character->player->client_id)); - - for(int i = 0; i < 2; i++) - flags[i]->reset(); - - game.create_sound_global(SOUND_CTF_CAPTURE); - } - } - } - else - { - CHARACTER *close_characters[MAX_CLIENTS]; - int num = game.world.find_entities(f->pos, 32.0f, (ENTITY**)close_characters, MAX_CLIENTS, NETOBJTYPE_CHARACTER); - for(int i = 0; i < num; i++) - { - if(close_characters[i]->team == f->team) - { - // return the flag - if(!f->at_stand) - { - CHARACTER *chr = close_characters[i]; - chr->player->score += 1; - - dbg_msg("game", "flag_return player='%d:%s'", - chr->player->client_id, - server_clientname(chr->player->client_id)); - - game.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_character = close_characters[i]; - f->carrying_character->player->score += 1; - - dbg_msg("game", "flag_grab player='%d:%s'", - f->carrying_character->player->client_id, - server_clientname(f->carrying_character->player->client_id)); - - for(int c = 0; c < MAX_CLIENTS; c++) - { - if(game.players[c].client_id == -1) - continue; - - if(game.players[c].team == fi) - game.create_sound_global(SOUND_CTF_GRAB_EN, game.players[c].client_id); - else - game.create_sound_global(SOUND_CTF_GRAB_PL, game.players[c].client_id); - } - break; - } - } - - if(!f->carrying_character && !f->at_stand) - { - if(server_tick() > f->drop_tick + server_tickspeed()*30) - { - game.create_sound_global(SOUND_CTF_RETURN); - f->reset(); - } - else - { - f->vel.y += game.world.core.tuning.gravity; - move_box(&f->pos, &f->vel, vec2(f->phys_size, f->phys_size), 0.5f); - } - } - } - } -} - -// Flag -FLAG::FLAG(int _team) -: ENTITY(NETOBJTYPE_FLAG) -{ - team = _team; - proximity_radius = phys_size; - carrying_character = 0x0; - - reset(); - - // TODO: should this be done here? - game.world.insert_entity(this); -} - -void FLAG::reset() -{ - carrying_character = 0; - at_stand = 1; - pos = stand_pos; - vel = vec2(0,0); -} - -void FLAG::snap(int snapping_client) -{ - NETOBJ_FLAG *flag = (NETOBJ_FLAG *)snap_new_item(NETOBJTYPE_FLAG, team, sizeof(NETOBJ_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_character) - flag->carried_by = carrying_character->player->client_id; -} diff --git a/src/game/server/gs_game_ctf.hpp b/src/game/server/gs_game_ctf.hpp deleted file mode 100644 index bf6282e7..00000000 --- a/src/game/server/gs_game_ctf.hpp +++ /dev/null @@ -1,33 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ - -// game object -class GAMECONTROLLER_CTF : public GAMECONTROLLER -{ -public: - class FLAG *flags[2]; - - GAMECONTROLLER_CTF(); - virtual void tick(); - - virtual bool on_entity(int index, vec2 pos); - virtual int on_character_death(class CHARACTER *victim, class PLAYER *killer, int weapon); -}; - -// TODO: move to seperate file -class FLAG : public ENTITY -{ -public: - static const int phys_size = 14; - CHARACTER *carrying_character; - 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 deleted file mode 100644 index 264063bf..00000000 --- a/src/game/server/gs_game_dm.cpp +++ /dev/null @@ -1,10 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "gs_common.hpp" -#include "gs_game_dm.hpp" - -void GAMECONTROLLER_DM::tick() -{ - do_player_score_wincheck(); - GAMECONTROLLER::tick(); -} diff --git a/src/game/server/gs_game_dm.hpp b/src/game/server/gs_game_dm.hpp deleted file mode 100644 index 99ceaec1..00000000 --- a/src/game/server/gs_game_dm.hpp +++ /dev/null @@ -1,7 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -// game object -class GAMECONTROLLER_DM : public GAMECONTROLLER -{ -public: - virtual void tick(); -}; diff --git a/src/game/server/gs_game_tdm.cpp b/src/game/server/gs_game_tdm.cpp deleted file mode 100644 index d865a65a..00000000 --- a/src/game/server/gs_game_tdm.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -#include -#include "gs_common.hpp" -#include "gs_game_tdm.hpp" - -GAMECONTROLLER_TDM::GAMECONTROLLER_TDM() -{ - is_teamplay = true; -} - -int GAMECONTROLLER_TDM::on_character_death(class CHARACTER *victim, class PLAYER *killer, int weapon) -{ - GAMECONTROLLER::on_character_death(victim, killer, weapon); - - if(weapon >= 0) - { - // do team scoring - if(killer == victim->player) - teamscore[killer->team&1]--; // klant arschel - else - teamscore[killer->team&1]++; // good shit - } - return 0; -} - -void GAMECONTROLLER_TDM::tick() -{ - do_team_score_wincheck(); - GAMECONTROLLER::tick(); -} diff --git a/src/game/server/gs_game_tdm.hpp b/src/game/server/gs_game_tdm.hpp deleted file mode 100644 index bb35260c..00000000 --- a/src/game/server/gs_game_tdm.hpp +++ /dev/null @@ -1,10 +0,0 @@ -/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */ -// game object -class GAMECONTROLLER_TDM : public GAMECONTROLLER -{ -public: - GAMECONTROLLER_TDM(); - - int on_character_death(class CHARACTER *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 index 5af6758b..1d981ea5 100644 --- a/src/game/server/gs_server.cpp +++ b/src/game/server/gs_server.cpp @@ -11,9 +11,10 @@ #include #include #include "gs_common.hpp" -#include "gs_game_ctf.hpp" -#include "gs_game_tdm.hpp" -#include "gs_game_dm.hpp" + +#include "gamemodes/dm.hpp" +#include "gamemodes/tdm.hpp" +#include "gamemodes/ctf.hpp" TUNING_PARAMS tuning; GAMECONTEXT game; @@ -106,12 +107,12 @@ void send_tuning_params(int cid) ////////////////////////////////////////////////// // Event handler ////////////////////////////////////////////////// -EVENT_HANDLER::EVENT_HANDLER() +EVENTHANDLER::EVENTHANDLER() { clear(); } -void *EVENT_HANDLER::create(int type, int size, int mask) +void *EVENTHANDLER::create(int type, int size, int mask) { if(num_events == MAX_EVENTS) return 0; @@ -128,13 +129,13 @@ void *EVENT_HANDLER::create(int type, int size, int mask) return p; } -void EVENT_HANDLER::clear() +void EVENTHANDLER::clear() { num_events = 0; current_offset = 0; } -void EVENT_HANDLER::snap(int snapping_client) +void EVENTHANDLER::snap(int snapping_client) { for(int i = 0; i < num_events; i++) { @@ -327,217 +328,6 @@ void GAMEWORLD::tick() remove_entities(); } -////////////////////////////////////////////////// -// projectile -////////////////////////////////////////////////// -PROJECTILE::PROJECTILE(int type, int owner, vec2 pos, vec2 dir, int span, ENTITY* powner, - int damage, int flags, float force, int sound_impact, int weapon) -: ENTITY(NETOBJTYPE_PROJECTILE) -{ - this->type = type; - this->pos = pos; - this->direction = dir; - 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(); - game.world.insert_entity(this); -} - -void PROJECTILE::reset() -{ - game.world.destroy_entity(this); -} - -vec2 PROJECTILE::get_pos(float time) -{ - float curvature = 0; - float speed = 0; - if(type == WEAPON_GRENADE) - { - curvature = tuning.grenade_curvature; - speed = tuning.grenade_speed; - } - else if(type == WEAPON_SHOTGUN) - { - curvature = tuning.shotgun_curvature; - speed = tuning.shotgun_speed; - } - else if(type == WEAPON_GUN) - { - curvature = tuning.gun_curvature; - speed = tuning.gun_speed; - } - - return calc_pos(pos, direction, curvature, speed, time); -} - - -void PROJECTILE::tick() -{ - - float pt = (server_tick()-start_tick-1)/(float)server_tickspeed(); - float ct = (server_tick()-start_tick)/(float)server_tickspeed(); - vec2 prevpos = get_pos(pt); - vec2 curpos = get_pos(ct); - - lifespan--; - - int collide = col_intersect_line(prevpos, curpos, &curpos); - //int collide = col_check_point((int)curpos.x, (int)curpos.y); - - CHARACTER *targetchr = game.world.intersect_character(prevpos, curpos, 6.0f, curpos, powner); - if(targetchr || collide || lifespan < 0) - { - if(lifespan >= 0 || weapon == WEAPON_GRENADE) - game.create_sound(curpos, sound_impact); - - if(flags & PROJECTILE_FLAGS_EXPLODE) - game.create_explosion(curpos, owner, weapon, false); - else if(targetchr) - { - targetchr->take_damage(direction * max(0.001f, force), damage, owner, weapon); - } - - game.world.destroy_entity(this); - } -} - -void PROJECTILE::fill_info(NETOBJ_PROJECTILE *proj) -{ - proj->x = (int)pos.x; - proj->y = (int)pos.y; - proj->vx = (int)(direction.x*100.0f); - proj->vy = (int)(direction.y*100.0f); - proj->start_tick = start_tick; - proj->type = type; -} - -void PROJECTILE::snap(int snapping_client) -{ - float ct = (server_tick()-start_tick)/(float)server_tickspeed(); - - if(distance(game.players[snapping_client].view_pos, get_pos(ct)) > 1000.0f) - return; - - NETOBJ_PROJECTILE *proj = (NETOBJ_PROJECTILE *)snap_new_item(NETOBJTYPE_PROJECTILE, id, sizeof(NETOBJ_PROJECTILE)); - fill_info(proj); -} - - -////////////////////////////////////////////////// -// laser -////////////////////////////////////////////////// -LASER::LASER(vec2 pos, vec2 direction, float start_energy, CHARACTER *owner) -: ENTITY(NETOBJTYPE_LASER) -{ - this->pos = pos; - this->owner = owner; - energy = start_energy; - dir = direction; - bounces = 0; - do_bounce(); - - game.world.insert_entity(this); -} - - -bool LASER::hit_character(vec2 from, vec2 to) -{ - vec2 at; - CHARACTER *hit = game.world.intersect_character(pos, to, 0.0f, at, owner); - if(!hit) - return false; - - this->from = from; - pos = at; - energy = -1; - hit->take_damage(vec2(0,0), tuning.laser_damage, owner->player->client_id, WEAPON_RIFLE); - return true; -} - -void LASER::do_bounce() -{ - eval_tick = server_tick(); - - if(energy < 0) - { - //dbg_msg("laser", "%d removed", server_tick()); - game.world.destroy_entity(this); - return; - } - - vec2 to = pos + dir*energy; - - if(col_intersect_line(pos, to, &to)) - { - if(!hit_character(pos, to)) - { - // intersected - from = pos; - pos = to - dir*2; - vec2 temp_pos = pos; - vec2 temp_dir = dir*4.0f; - - move_point(&temp_pos, &temp_dir, 1.0f, 0); - pos = temp_pos; - dir = normalize(temp_dir); - - energy -= distance(from, pos) + tuning.laser_bounce_cost; - bounces++; - - if(bounces > tuning.laser_bounce_num) - energy = -1; - - game.create_sound(pos, SOUND_RIFLE_BOUNCE); - } - } - else - { - if(!hit_character(pos, to)) - { - from = pos; - pos = to; - energy = -1; - } - } - - //dbg_msg("laser", "%d done %f %f %f %f", server_tick(), from.x, from.y, pos.x, pos.y); -} - -void LASER::reset() -{ - game.world.destroy_entity(this); -} - -void LASER::tick() -{ - if(server_tick() > eval_tick+(server_tickspeed()*tuning.laser_bounce_delay)/1000.0f) - { - do_bounce(); - } - -} - -void LASER::snap(int snapping_client) -{ - if(distance(game.players[snapping_client].view_pos, pos) > 1000.0f) - return; - - NETOBJ_LASER *obj = (NETOBJ_LASER *)snap_new_item(NETOBJTYPE_LASER, id, sizeof(NETOBJ_LASER)); - obj->x = (int)pos.x; - obj->y = (int)pos.y; - obj->from_x = (int)from.x; - obj->from_y = (int)from.y; - obj->start_tick = eval_tick; -} - GAMECONTEXT::GAMECONTEXT() { clear(); diff --git a/src/game/server/player.hpp b/src/game/server/player.hpp new file mode 100644 index 00000000..c1866f85 --- /dev/null +++ b/src/game/server/player.hpp @@ -0,0 +1,55 @@ + +// player object +class PLAYER +{ +public: + PLAYER(); + + // TODO: clean this up + char skin_name[64]; + int use_custom_color; + int color_body; + int color_feet; + + // + bool spawning; + int client_id; + int team; + int score; + + // + int64 last_chat; + + // network latency calculations + struct + { + int accum; + int accum_min; + int accum_max; + int avg; + int min; + int max; + } latency; + + CHARACTER character; + + // this is used for snapping so we know how we can clip the view for the player + vec2 view_pos; + + void init(int client_id); + + CHARACTER *get_character(); + + void kill_character(); + + void try_respawn(); + void respawn(); + void set_team(int team); + + void tick(); + void snap(int snaping_client); + + void on_direct_input(NETOBJ_PLAYER_INPUT *new_input); + void on_predicted_input(NETOBJ_PLAYER_INPUT *new_input); + void on_disconnect(); +}; -- cgit 1.4.1