Added TeeWorlds - it compiles but does not work yet (crashed my Evo yay!)

This commit is contained in:
pelya
2010-09-03 18:39:44 +03:00
parent ab7d052202
commit 59e15688fc
213 changed files with 48972 additions and 1 deletions

View File

@@ -0,0 +1,838 @@
#include <new>
#include <engine/e_server_interface.h>
#include <engine/e_config.h>
#include <game/server/gamecontext.hpp>
#include <game/mapitems.hpp>
#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;
}
MACRO_ALLOC_POOL_ID_IMPL(CHARACTER, MAX_CLIENTS)
// player
CHARACTER::CHARACTER()
: ENTITY(NETOBJTYPE_CHARACTER)
{
proximity_radius = phys_size;
}
void CHARACTER::reset()
{
destroy();
}
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;
reckoning_tick = 0;
mem_zero(&sendcore, sizeof(sendcore));
mem_zero(&reckoningcore, sizeof(reckoningcore));
game.world.insert_entity(this);
alive = true;
player->force_balanced = false;
game.controller->on_character_spawn(this);
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()
{
if(active_weapon != WEAPON_NINJA)
return 0;
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);
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)
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)
{
// 125ms is a magical limit of how fast a human can click
reload_timer = 125 * server_tickspeed() / 1000;;
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 hits = 0;
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_hammerhit(pos);
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;
hits++;
}
// if we hit anything, we have to wait for the reload
if(hits)
reload_timer = server_tickspeed()/3;
} break;
case WEAPON_GUN:
{
PROJECTILE *proj = new PROJECTILE(WEAPON_GUN,
player->client_id,
projectile_startpos,
direction,
(int)(server_tickspeed()*tuning.gun_lifetime),
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),
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),
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, player->client_id);
game.create_sound(pos, SOUND_RIFLE_FIRE);
} break;
case WEAPON_NINJA:
{
attack_tick = server_tick();
ninja.activationdir = direction;
ninja.currentmovetime = data->weapons.ninja.movetime * server_tickspeed() / 1000;
//reload_timer = data->weapons.ninja.base->firedelay * server_tickspeed() / 1000 + server_tick();
// reset hit objects
numobjectshit = 0;
game.create_sound(pos, SOUND_NINJA_FIRE);
} break;
}
if(weapons[active_weapon].ammo > 0) // -1 == unlimited
weapons[active_weapon].ammo--;
attack_tick = server_tick();
if(!reload_timer)
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--;
} */
//if(active_weapon == WEAPON_NINJA)
handle_ninja();
// 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();
}
mem_copy(&latest_previnput, &latest_input, sizeof(latest_input));
}
void CHARACTER::tick()
{
if(player->force_balanced)
{
char buf[128];
str_format(buf, sizeof(buf), "You were moved to %s due to team balancing", game.controller->get_team_name(team));
game.send_broadcast(buf, player->client_id);
player->force_balanced = false;
}
//player_core core;
//core.pos = pos;
//core.jumped = jumped;
core.input = input;
core.tick(true);
float phys_size = 28.0f;
// handle death-tiles
if(col_get((int)(pos.x+phys_size/2), (int)(pos.y-phys_size/2))&COLFLAG_DEATH ||
col_get((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2))&COLFLAG_DEATH ||
col_get((int)(pos.x-phys_size/2), (int)(pos.y-phys_size/2))&COLFLAG_DEATH ||
col_get((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2))&COLFLAG_DEATH)
{
die(player->client_id, WEAPON_WORLD);
}
// handle weapons
handle_weapons();
player_state = input.player_state;
// Previnput
previnput = input;
return;
}
void CHARACTER::tick_defered()
{
// advance the dummy
{
WORLD_CORE tempworld;
reckoningcore.world = &tempworld;
reckoningcore.tick(false);
reckoningcore.move();
reckoningcore.quantize();
}
//lastsentcore;
/*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_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_HIT_NOHOOK) game.create_sound(pos, SOUND_HOOK_NOATTACH, 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;
}
// update the sendcore if needed
{
NETOBJ_CHARACTER predicted;
NETOBJ_CHARACTER current;
mem_zero(&predicted, sizeof(predicted));
mem_zero(&current, sizeof(current));
reckoningcore.write(&predicted);
core.write(&current);
// only allow dead reackoning for a top of 3 seconds
if(reckoning_tick+server_tickspeed()*3 < server_tick() || mem_comp(&predicted, &current, sizeof(NETOBJ_CHARACTER)) != 0)
{
reckoning_tick = server_tick();
sendcore = core;
reckoningcore = core;
}
}
}
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;
*/
// this is for auto respawn after 3 secs
player->die_tick = server_tick();
alive = false;
game.world.remove_entity(this);
game.world.core.characters[player->client_id] = 0;
game.create_death(pos, player->client_id);
// we got to wait 0.5 secs before respawning
player->respawn_tick = server_tick()+server_tickspeed()/2;
}
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);
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.players[from])
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 && game.players[from])
{
CHARACTER *chr = game.players[from]->get_character();
if (chr)
{
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 snapping_client)
{
if(networkclipped(snapping_client))
return;
NETOBJ_CHARACTER *character = (NETOBJ_CHARACTER *)snap_new_item(NETOBJTYPE_CHARACTER, player->client_id, sizeof(NETOBJ_CHARACTER));
// write down the core
if(game.world.paused)
{
// no dead reckoning when paused because the client doesn't know
// how far to perform the reckoning
character->tick = 0;
core.write(character);
}
else
{
character->tick = reckoning_tick;
sendcore.write(character);
}
// set emote
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->direction = input.direction;
if(player->client_id == snapping_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;
}

View File

@@ -0,0 +1,134 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#ifndef GAME_SERVER_ENTITY_CHARACTER_H
#define GAME_SERVER_ENTITY_CHARACTER_H
#include <game/server/entity.hpp>
#include <game/generated/gs_data.hpp>
#include <game/generated/g_protocol.hpp>
#include <game/gamecore.hpp>
enum
{
WEAPON_GAME = -3, // team switching etc
WEAPON_SELF = -2, // console kill command
WEAPON_WORLD = -1, // death tiles etc
};
class CHARACTER : public ENTITY
{
MACRO_ALLOC_POOL_ID()
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 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;
// info for dead reckoning
int reckoning_tick; // tick that we are performing dead reckoning from
CHARACTER_CORE sendcore; // core that we should send
CHARACTER_CORE reckoningcore; // the dead reckoning 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 snapping_client);
bool increase_health(int amount);
bool increase_armor(int amount);
};
#endif

View File

@@ -0,0 +1,115 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <engine/e_server_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/server/gamecontext.hpp>
#include "laser.hpp"
//////////////////////////////////////////////////
// laser
//////////////////////////////////////////////////
LASER::LASER(vec2 pos, vec2 direction, float start_energy, int 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 *owner_char = game.get_player_char(owner);
CHARACTER *hit = game.world.intersect_character(pos, to, 0.0f, at, owner_char);
if(!hit)
return false;
this->from = from;
pos = at;
energy = -1;
hit->take_damage(vec2(0,0), tuning.laser_damage, owner, 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;
vec2 org_to = to;
if(col_intersect_line(pos, to, 0x0, &to))
{
if(!hit_character(pos, to))
{
// intersected
from = pos;
pos = to;
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(networkclipped(snapping_client))
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;
}

View File

@@ -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 <game/server/entity.hpp>
class CHARACTER;
class LASER : public ENTITY
{
vec2 from;
vec2 dir;
float energy;
int bounces;
int eval_tick;
int owner;
bool hit_character(vec2 from, vec2 to);
void do_bounce();
public:
LASER(vec2 pos, vec2 direction, float start_energy, int owner);
virtual void reset();
virtual void tick();
virtual void snap(int snapping_client);
};
#endif

View File

@@ -0,0 +1,144 @@
#include <engine/e_server_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/server/gamecontext.hpp>
#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 && chr->alive)
{
// 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->weapons[WEAPON_NINJA].ammo = -1;
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;
}

View File

@@ -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 <game/server/entity.hpp>
// 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

View File

@@ -0,0 +1,105 @@
#include <engine/e_server_interface.h>
#include <game/generated/g_protocol.hpp>
#include <game/server/gamecontext.hpp>
#include "projectile.hpp"
//////////////////////////////////////////////////
// projectile
//////////////////////////////////////////////////
PROJECTILE::PROJECTILE(int type, int owner, vec2 pos, vec2 dir, int span,
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->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, 0);
//int collide = col_check_point((int)curpos.x, (int)curpos.y);
CHARACTER *ownerchar = game.get_player_char(owner);
CHARACTER *targetchr = game.world.intersect_character(prevpos, curpos, 6.0f, curpos, ownerchar);
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(networkclipped(snapping_client, get_pos(ct)))
return;
NETOBJ_PROJECTILE *proj = (NETOBJ_PROJECTILE *)snap_new_item(NETOBJTYPE_PROJECTILE, id, sizeof(NETOBJ_PROJECTILE));
fill_info(proj);
}

View File

@@ -0,0 +1,37 @@
/* 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;
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,
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

View File

@@ -0,0 +1,49 @@
#include <engine/e_server_interface.h>
#include "entity.hpp"
#include "gamecontext.hpp"
//////////////////////////////////////////////////
// Entity
//////////////////////////////////////////////////
ENTITY::ENTITY(int objtype)
{
this->objtype = objtype;
pos = vec2(0,0);
proximity_radius = 0;
marked_for_destroy = false;
id = snap_new_id();
next_entity = 0;
prev_entity = 0;
prev_type_entity = 0;
next_type_entity = 0;
}
ENTITY::~ENTITY()
{
game.world.remove_entity(this);
snap_free_id(id);
}
int ENTITY::networkclipped(int snapping_client)
{
return networkclipped(snapping_client, pos);
}
int ENTITY::networkclipped(int snapping_client, vec2 check_pos)
{
if(snapping_client == -1)
return 0;
float dx = game.players[snapping_client]->view_pos.x-check_pos.x;
float dy = game.players[snapping_client]->view_pos.y-check_pos.y;
if(fabs(dx) > 1000.0f || fabs(dy) > 800.0f)
return 1;
if(distance(game.players[snapping_client]->view_pos, check_pos) > 1100.0f)
return 1;
return 0;
}

View File

@@ -0,0 +1,145 @@
#ifndef GAME_SERVER_ENTITY_H
#define GAME_SERVER_ENTITY_H
#include <new>
#include <base/vmath.hpp>
#define MACRO_ALLOC_HEAP() \
public: \
void *operator new(size_t size) \
{ \
void *p = mem_alloc(size, 1); \
/*dbg_msg("", "++ %p %d", p, size);*/ \
mem_zero(p, size); \
return p; \
} \
void operator delete(void *p) \
{ \
/*dbg_msg("", "-- %p", p);*/ \
mem_free(p); \
} \
private:
#define MACRO_ALLOC_POOL_ID() \
public: \
void *operator new(size_t size, int id); \
void operator delete(void *p); \
private:
#define MACRO_ALLOC_POOL_ID_IMPL(POOLTYPE, poolsize) \
static char pool_data_##POOLTYPE[poolsize][sizeof(POOLTYPE)] = {{0}}; \
static int pool_used_##POOLTYPE[poolsize] = {0}; \
void *POOLTYPE::operator new(size_t size, int id) \
{ \
dbg_assert(sizeof(POOLTYPE) == size, "size error"); \
dbg_assert(!pool_used_##POOLTYPE[id], "already used"); \
/*dbg_msg("pool", "++ %s %d", #POOLTYPE, id);*/ \
pool_used_##POOLTYPE[id] = 1; \
mem_zero(pool_data_##POOLTYPE[id], size); \
return pool_data_##POOLTYPE[id]; \
} \
void POOLTYPE::operator delete(void *p) \
{ \
int id = (POOLTYPE*)p - (POOLTYPE*)pool_data_##POOLTYPE; \
dbg_assert(pool_used_##POOLTYPE[id], "not used"); \
/*dbg_msg("pool", "-- %s %d", #POOLTYPE, id);*/ \
pool_used_##POOLTYPE[id] = 0; \
mem_zero(pool_data_##POOLTYPE[id], sizeof(POOLTYPE)); \
}
/*
Class: Entity
Basic entity class.
*/
class ENTITY
{
MACRO_ALLOC_HEAP()
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) {}
/*
Function: networkclipped(int snapping_client)
Performs a series of test to see if a client can see the
entity.
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.
Returns:
Non-zero if the entity doesn't have to be in the snapshot.
*/
int networkclipped(int snapping_client);
int networkclipped(int snapping_client, vec2 check_pos);
/*
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

View File

@@ -0,0 +1,49 @@
#include "eventhandler.hpp"
#include "gamecontext.hpp"
//////////////////////////////////////////////////
// Event handler
//////////////////////////////////////////////////
EVENTHANDLER::EVENTHANDLER()
{
clear();
}
void *EVENTHANDLER::create(int type, int size, int mask)
{
if(num_events == MAX_EVENTS)
return 0;
if(current_offset+size >= MAX_DATASIZE)
return 0;
void *p = &data[current_offset];
offsets[num_events] = current_offset;
types[num_events] = type;
sizes[num_events] = size;
client_masks[num_events] = mask;
current_offset += size;
num_events++;
return p;
}
void EVENTHANDLER::clear()
{
num_events = 0;
current_offset = 0;
}
void EVENTHANDLER::snap(int snapping_client)
{
for(int i = 0; i < num_events; i++)
{
if(snapping_client == -1 || cmask_is_set(client_masks[i], snapping_client))
{
NETEVENT_COMMON *ev = (NETEVENT_COMMON *)&data[offsets[i]];
if(snapping_client == -1 || distance(game.players[snapping_client]->view_pos, vec2(ev->x, ev->y)) < 1500.0f)
{
void *d = snap_new_item(types[i], i, sizes[i]);
mem_copy(d, &data[offsets[i]], sizes[i]);
}
}
}
}

View File

@@ -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

View File

@@ -0,0 +1,377 @@
#include <string.h>
#include <new>
#include <engine/e_server_interface.h>
#include "gamecontext.hpp"
GAMECONTEXT game;
GAMECONTEXT::GAMECONTEXT()
{
for(int i = 0; i < MAX_CLIENTS; i++)
players[i] = 0;
controller = 0;
vote_closetime = 0;
}
GAMECONTEXT::~GAMECONTEXT()
{
for(int i = 0; i < MAX_CLIENTS; i++)
delete players[i];
}
void GAMECONTEXT::clear()
{
this->~GAMECONTEXT();
mem_zero(this, sizeof(*this));
new (this) GAMECONTEXT();
}
class CHARACTER *GAMECONTEXT::get_player_char(int client_id)
{
if(client_id < 0 || client_id >= MAX_CLIENTS || !players[client_id])
return 0;
return players[client_id]->get_character();
}
void GAMECONTEXT::create_damageind(vec2 p, float angle, int amount)
{
float a = 3 * 3.14159f / 2 + angle;
//float a = get_angle(dir);
float s = a-pi/3;
float e = a+pi/3;
for(int i = 0; i < amount; i++)
{
float f = mix(s, e, float(i+1)/float(amount+2));
NETEVENT_DAMAGEIND *ev = (NETEVENT_DAMAGEIND *)events.create(NETEVENTTYPE_DAMAGEIND, sizeof(NETEVENT_DAMAGEIND));
if(ev)
{
ev->x = (int)p.x;
ev->y = (int)p.y;
ev->angle = (int)(f*256.0f);
}
}
}
void GAMECONTEXT::create_hammerhit(vec2 p)
{
// create the event
NETEVENT_HAMMERHIT *ev = (NETEVENT_HAMMERHIT *)events.create(NETEVENTTYPE_HAMMERHIT, sizeof(NETEVENT_HAMMERHIT));
if(ev)
{
ev->x = (int)p.x;
ev->y = (int)p.y;
}
}
void GAMECONTEXT::create_explosion(vec2 p, int owner, int weapon, bool bnodamage)
{
// create the event
NETEVENT_EXPLOSION *ev = (NETEVENT_EXPLOSION *)events.create(NETEVENTTYPE_EXPLOSION, sizeof(NETEVENT_EXPLOSION));
if(ev)
{
ev->x = (int)p.x;
ev->y = (int)p.y;
}
if (!bnodamage)
{
// deal damage
CHARACTER *ents[64];
float radius = 135.0f;
float innerradius = 48.0f;
int num = game.world.find_entities(p, radius, (ENTITY**)ents, 64, NETOBJTYPE_CHARACTER);
for(int i = 0; i < num; i++)
{
vec2 diff = ents[i]->pos - p;
vec2 forcedir(0,1);
float l = length(diff);
if(l)
forcedir = normalize(diff);
l = 1-clamp((l-innerradius)/(radius-innerradius), 0.0f, 1.0f);
float dmg = 6 * l;
if((int)dmg)
ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner, weapon);
}
}
}
/*
void create_smoke(vec2 p)
{
// create the event
EV_EXPLOSION *ev = (EV_EXPLOSION *)events.create(EVENT_SMOKE, sizeof(EV_EXPLOSION));
if(ev)
{
ev->x = (int)p.x;
ev->y = (int)p.y;
}
}*/
void GAMECONTEXT::create_playerspawn(vec2 p)
{
// create the event
NETEVENT_SPAWN *ev = (NETEVENT_SPAWN *)events.create(NETEVENTTYPE_SPAWN, sizeof(NETEVENT_SPAWN));
if(ev)
{
ev->x = (int)p.x;
ev->y = (int)p.y;
}
}
void GAMECONTEXT::create_death(vec2 p, int cid)
{
// create the event
NETEVENT_DEATH *ev = (NETEVENT_DEATH *)events.create(NETEVENTTYPE_DEATH, sizeof(NETEVENT_DEATH));
if(ev)
{
ev->x = (int)p.x;
ev->y = (int)p.y;
ev->cid = cid;
}
}
void GAMECONTEXT::create_sound(vec2 pos, int sound, int mask)
{
if (sound < 0)
return;
// create a sound
NETEVENT_SOUNDWORLD *ev = (NETEVENT_SOUNDWORLD *)events.create(NETEVENTTYPE_SOUNDWORLD, sizeof(NETEVENT_SOUNDWORLD), mask);
if(ev)
{
ev->x = (int)pos.x;
ev->y = (int)pos.y;
ev->soundid = sound;
}
}
void GAMECONTEXT::create_sound_global(int sound, int target)
{
if (sound < 0)
return;
NETMSG_SV_SOUNDGLOBAL msg;
msg.soundid = sound;
msg.pack(MSGFLAG_VITAL);
server_send_msg(target);
}
void GAMECONTEXT::send_chat_target(int to, const char *text)
{
NETMSG_SV_CHAT msg;
msg.team = 0;
msg.cid = -1;
msg.message = text;
msg.pack(MSGFLAG_VITAL);
server_send_msg(to);
}
void GAMECONTEXT::send_chat(int chatter_cid, int team, const char *text)
{
if(chatter_cid >= 0 && chatter_cid < MAX_CLIENTS)
dbg_msg("chat", "%d:%d:%s: %s", chatter_cid, team, server_clientname(chatter_cid), text);
else
dbg_msg("chat", "*** %s", text);
if(team == CHAT_ALL)
{
NETMSG_SV_CHAT msg;
msg.team = 0;
msg.cid = chatter_cid;
msg.message = text;
msg.pack(MSGFLAG_VITAL);
server_send_msg(-1);
}
else
{
NETMSG_SV_CHAT msg;
msg.team = 1;
msg.cid = chatter_cid;
msg.message = text;
msg.pack(MSGFLAG_VITAL);
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(game.players[i] && game.players[i]->team == team)
server_send_msg(i);
}
}
}
void GAMECONTEXT::send_emoticon(int cid, int emoticon)
{
NETMSG_SV_EMOTICON msg;
msg.cid = cid;
msg.emoticon = emoticon;
msg.pack(MSGFLAG_VITAL);
server_send_msg(-1);
}
void GAMECONTEXT::send_weapon_pickup(int cid, int weapon)
{
NETMSG_SV_WEAPONPICKUP msg;
msg.weapon = weapon;
msg.pack(MSGFLAG_VITAL);
server_send_msg(cid);
}
void GAMECONTEXT::send_broadcast(const char *text, int cid)
{
NETMSG_SV_BROADCAST msg;
msg.message = text;
msg.pack(MSGFLAG_VITAL);
server_send_msg(cid);
}
//
void GAMECONTEXT::start_vote(const char *desc, const char *command)
{
// check if a vote is already running
if(vote_closetime)
return;
// reset votes
vote_enforce = VOTE_ENFORCE_UNKNOWN;
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(players[i])
players[i]->vote = 0;
}
// start vote
vote_closetime = time_get() + time_freq()*25;
str_copy(vote_description, desc, sizeof(vote_description));
str_copy(vote_command, command, sizeof(vote_description));
send_vote_set(-1);
send_vote_status(-1);
}
void GAMECONTEXT::end_vote()
{
vote_closetime = 0;
send_vote_set(-1);
}
void GAMECONTEXT::send_vote_set(int cid)
{
NETMSG_SV_VOTE_SET msg;
if(vote_closetime)
{
msg.timeout = (vote_closetime-time_get())/time_freq();
msg.description = vote_description;
msg.command = vote_command;
}
else
{
msg.timeout = 0;
msg.description = "";
msg.command = "";
}
msg.pack(MSGFLAG_VITAL);
server_send_msg(cid);
}
void GAMECONTEXT::send_vote_status(int cid)
{
NETMSG_SV_VOTE_STATUS msg = {0};
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(players[i])
{
msg.total++;
if(players[i]->vote > 0)
msg.yes++;
else if(players[i]->vote < 0)
msg.no++;
else
msg.pass++;
}
}
msg.pack(MSGFLAG_VITAL);
server_send_msg(cid);
}
void GAMECONTEXT::abort_vote_kick_on_disconnect(int client_id)
{
if(vote_closetime && !strncmp(vote_command, "kick ", 5) && atoi(&vote_command[5]) == client_id)
vote_closetime = -1;
}
void GAMECONTEXT::tick()
{
world.core.tuning = tuning;
world.tick();
//if(world.paused) // make sure that the game object always updates
controller->tick();
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(players[i])
players[i]->tick();
}
// update voting
if(vote_closetime)
{
// abort the kick-vote on player-leave
if(vote_closetime == -1)
{
send_chat(-1, GAMECONTEXT::CHAT_ALL, "Vote aborted");
end_vote();
}
else
{
// count votes
int total = 0, yes = 0, no = 0;
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(players[i])
{
total++;
if(players[i]->vote > 0)
yes++;
else if(players[i]->vote < 0)
no++;
}
}
if(vote_enforce == VOTE_ENFORCE_YES || yes >= total/2+1)
{
console_execute_line(vote_command);
end_vote();
send_chat(-1, GAMECONTEXT::CHAT_ALL, "Vote passed");
if(players[vote_creator])
players[vote_creator]->last_votecall = 0;
}
else if(vote_enforce == VOTE_ENFORCE_NO || time_get() > vote_closetime || no >= total/2+1 || yes+no == total)
{
end_vote();
send_chat(-1, GAMECONTEXT::CHAT_ALL, "Vote failed");
}
}
}
}
void GAMECONTEXT::snap(int client_id)
{
world.snap(client_id);
controller->snap(client_id);
events.snap(client_id);
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(players[i])
players[i]->snap(client_id);
}
}

View File

@@ -0,0 +1,105 @@
#ifndef GAME_SERVER_GAMECONTEXT_H
#define GAME_SERVER_GAMECONTEXT_H
#include "eventhandler.hpp"
#include "gamecontroller.hpp"
#include "gameworld.hpp"
#include "player.hpp"
/*
Tick
Game Context (GAMECONTEXT::tick)
Game World (GAMEWORLD::tick)
Reset world if requested (GAMEWORLD::reset)
All entities in the world (ENTITY::tick)
All entities in the world (ENTITY::tick_defered)
Remove entities marked for deletion (GAMEWORLD::remove_entities)
Game Controller (GAMECONTROLLER::tick)
All players (PLAYER::tick)
Snap
Game Context (GAMECONTEXT::snap)
Game World (GAMEWORLD::snap)
All entities in the world (ENTITY::snap)
Game Controller (GAMECONTROLLER::snap)
Events handler (EVENT_HANDLER::snap)
All players (PLAYER::snap)
*/
class GAMECONTEXT
{
public:
GAMECONTEXT();
~GAMECONTEXT();
void clear();
EVENTHANDLER events;
PLAYER *players[MAX_CLIENTS];
GAMECONTROLLER *controller;
GAMEWORLD world;
void tick();
void snap(int client_id);
// helper functions
class CHARACTER *get_player_char(int client_id);
// voting
void start_vote(const char *desc, const char *command);
void end_vote();
void send_vote_set(int cid);
void send_vote_status(int cid);
void abort_vote_kick_on_disconnect(int client_id);
int vote_creator;
int64 vote_closetime;
char vote_description[512];
char vote_command[512];
int vote_enforce;
enum
{
VOTE_ENFORCE_UNKNOWN=0,
VOTE_ENFORCE_NO,
VOTE_ENFORCE_YES,
};
// 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_hammerhit(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);
enum
{
CHAT_ALL=-2,
CHAT_SPEC=-1,
CHAT_RED=0,
CHAT_BLUE=1
};
// network
void send_chat_target(int to, const char *text);
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);
};
extern GAMECONTEXT game;
// MISC stuff, move to a better place later on
extern TUNING_PARAMS tuning;
inline int cmask_all() { return -1; }
inline int cmask_one(int cid) { return 1<<cid; }
inline int cmask_all_except_one(int cid) { return 0x7fffffff^cmask_one(cid); }
inline bool cmask_is_set(int mask, int cid) { return (mask&cmask_one(cid)) != 0; }
#endif

View File

@@ -0,0 +1,671 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <string.h>
#include <engine/e_config.h>
#include <engine/e_server_interface.h>
#include <game/mapitems.hpp>
#include <game/generated/g_protocol.hpp>
#include "entities/pickup.hpp"
#include "gamecontroller.hpp"
#include "gamecontext.hpp"
GAMECONTROLLER::GAMECONTROLLER()
{
gametype = "unknown";
//
do_warmup(config.sv_warmup);
game_over_tick = -1;
sudden_death = 0;
round_start_tick = server_tick();
round_count = 0;
game_flags = 0;
teamscore[0] = 0;
teamscore[1] = 0;
map_wish[0] = 0;
unbalanced_tick = -1;
force_balanced = false;
num_spawn_points[0] = 0;
num_spawn_points[1] = 0;
num_spawn_points[2] = 0;
}
GAMECONTROLLER::~GAMECONTROLLER()
{
}
float GAMECONTROLLER::evaluate_spawn_pos(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;
}
return score;
}
void GAMECONTROLLER::evaluate_spawn_type(SPAWNEVAL *eval, int t)
{
// get spawn point
for(int i = 0; i < num_spawn_points[t]; i++)
{
vec2 p = spawn_points[t][i];
float s = evaluate_spawn_pos(eval, p);
if(!eval->got || eval->score > s)
{
eval->got = true;
eval->score = s;
eval->pos = p;
}
}
}
bool GAMECONTROLLER::can_spawn(PLAYER *player, vec2 *out_pos)
{
SPAWNEVAL eval;
// spectators can't spawn
if(player->team == -1)
return false;
if(is_teamplay())
{
eval.friendly_team = player->team;
// try first try own team spawn, then normal spawn and then enemy
evaluate_spawn_type(&eval, 1+(player->team&1));
if(!eval.got)
{
evaluate_spawn_type(&eval, 0);
if(!eval.got)
evaluate_spawn_type(&eval, 1+((player->team+1)&1));
}
}
else
{
evaluate_spawn_type(&eval, 0);
evaluate_spawn_type(&eval, 1);
evaluate_spawn_type(&eval, 2);
}
*out_pos = eval.pos;
return eval.got;
}
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;
unbalanced_tick = -1;
force_balanced = false;
dbg_msg("game","start round type='%s' teamplay='%d'", gametype, game_flags&GAMEFLAG_TEAMS);
}
void GAMECONTROLLER::change_map(const char *to_map)
{
str_copy(map_wish, to_map, sizeof(map_wish));
endround();
}
void GAMECONTROLLER::cyclemap()
{
if(map_wish[0] != 0)
{
dbg_msg("game", "rotating map to %s", map_wish);
str_copy(config.sv_map, map_wish, sizeof(config.sv_map));
map_wish[0] = 0;
round_count = 0;
return;
}
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++;
round_count = 0;
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])
{
game.players[i]->respawn();
game.players[i]->score = 0;
}
}
}
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::on_character_spawn(class CHARACTER *chr)
{
// default health
chr->health = 10;
// give default weapons
chr->weapons[WEAPON_HAMMER].got = 1;
chr->weapons[WEAPON_HAMMER].ammo = -1;
chr->weapons[WEAPON_GUN].got = 1;
chr->weapons[WEAPON_GUN].ammo = 10;
}
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] || !game.players[cid2])
return false;
if(game.players[cid1]->team == game.players[cid2]->team)
return true;
}
return false;
}
bool GAMECONTROLLER::is_force_balanced()
{
if(force_balanced)
{
force_balanced = false;
return true;
}
else
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();
round_count++;
}
}
// do team-balancing
if (is_teamplay() && unbalanced_tick != -1 && server_tick() > unbalanced_tick+config.sv_teambalance_time*server_tickspeed()*60)
{
dbg_msg("game", "Balancing teams");
int t[2] = {0,0};
int tscore[2] = {0,0};
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(game.players[i] && game.players[i]->team != -1)
{
t[game.players[i]->team]++;
tscore[game.players[i]->team]+=game.players[i]->score;
}
}
// are teams unbalanced?
if(abs(t[0]-t[1]) >= 2)
{
int m = (t[0] > t[1]) ? 0 : 1;
int num_balance = abs(t[0]-t[1]) / 2;
do
{
PLAYER *p = 0;
int pd = tscore[m];
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(!game.players[i])
continue;
// remember the player who would cause lowest score-difference
if(game.players[i]->team == m && (!p || abs((tscore[m^1]+game.players[i]->score) - (tscore[m]-game.players[i]->score)) < pd))
{
p = game.players[i];
pd = abs((tscore[m^1]+p->score) - (tscore[m]-p->score));
}
}
// move the player to other team without losing his score
// TODO: change in player::set_team needed: player won't lose score on team-change
int score_before = p->score;
p->set_team(m^1);
p->score = score_before;
p->respawn();
p->force_balanced = true;
} while (--num_balance);
force_balanced = true;
}
unbalanced_tick = -1;
}
// 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])
prog = max(prog, (game.players[i]->score*100)/config.sv_scorelimit);
}
}
}
if(warmup)
prog = -1;
server_setbrowseinfo(gametype, prog);
}
bool GAMECONTROLLER::is_teamplay() const
{
return game_flags&GAMEFLAG_TEAMS;
}
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->flags = game_flags;
gameobj->warmup = warmup;
gameobj->round_num = (strlen(config.sv_maprotation) && config.sv_rounds_per_map) ? config.sv_rounds_per_map : 0;
gameobj->round_current = round_count+1;
if(snapping_client == -1)
{
// we are recording a demo, just set the scores
gameobj->teamscore_red = teamscore[0];
gameobj->teamscore_blue = teamscore[1];
}
else
{
// TODO: this little hack should be removed
gameobj->teamscore_red = is_teamplay() ? teamscore[0] : game.players[snapping_client]->score;
gameobj->teamscore_blue = teamscore[1];
}
}
int GAMECONTROLLER::get_auto_team(int notthisid)
{
// this will force the auto balancer to work overtime aswell
if(config.dbg_stress)
return 0;
int numplayers[2] = {0,0};
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(game.players[i] && i != 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] && i != 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;
}
bool GAMECONTROLLER::check_team_balance()
{
if(!is_teamplay() || !config.sv_teambalance_time)
return true;
int t[2] = {0, 0};
for(int i = 0; i < MAX_CLIENTS; i++)
{
PLAYER *p = game.players[i];
if(p && p->team != -1)
t[p->team]++;
}
if(abs(t[0]-t[1]) >= 2)
{
dbg_msg("game", "Team is NOT balanced (red=%d blue=%d)", t[0], t[1]);
if (game.controller->unbalanced_tick == -1)
game.controller->unbalanced_tick = server_tick();
return false;
}
else
{
dbg_msg("game", "Team is balanced (red=%d blue=%d)", t[0], t[1]);
game.controller->unbalanced_tick = -1;
return true;
}
}
bool GAMECONTROLLER::can_change_team(PLAYER *pplayer, int jointeam)
{
int t[2] = {0, 0};
if (!is_teamplay() || jointeam == -1 || !config.sv_teambalance_time)
return true;
for(int i = 0; i < MAX_CLIENTS; i++)
{
PLAYER *p = game.players[i];
if(p && p->team != -1)
t[p->team]++;
}
// simulate what would happen if changed team
t[jointeam]++;
if (pplayer->team != -1)
t[jointeam^1]--;
// there is a player-difference of at least 2
if(abs(t[0]-t[1]) >= 2)
{
// player wants to join team with less players
if ((t[0] < t[1] && jointeam == 0) || (t[0] > t[1] && jointeam == 1))
return true;
else
return false;
}
else
return true;
}
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])
{
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;

View File

@@ -0,0 +1,136 @@
#ifndef GAME_SERVER_GAMECONTROLLER_H
#define GAME_SERVER_GAMECONTROLLER_H
#include <base/vmath.hpp>
/*
Class: Game Controller
Controls the main game logic. Keeping track of team and player score,
winning conditions and specific game logic.
*/
class GAMECONTROLLER
{
vec2 spawn_points[3][64];
int num_spawn_points[3];
protected:
struct SPAWNEVAL
{
SPAWNEVAL()
{
got = false;
friendly_team = -1;
pos = vec2(100,100);
}
vec2 pos;
bool got;
int friendly_team;
float score;
};
float evaluate_spawn_pos(SPAWNEVAL *eval, vec2 pos);
void evaluate_spawn_type(SPAWNEVAL *eval, int type);
bool evaluate_spawn(class PLAYER *p, vec2 *pos);
void cyclemap();
void resetgame();
char map_wish[128];
int round_start_tick;
int game_over_tick;
int sudden_death;
int teamscore[2];
int warmup;
int round_count;
int game_flags;
int unbalanced_tick;
bool force_balanced;
public:
const char *gametype;
bool is_teamplay() const;
GAMECONTROLLER();
virtual ~GAMECONTROLLER();
void do_team_score_wincheck();
void do_player_score_wincheck();
void do_warmup(int seconds);
void startround();
void endround();
void change_map(const char *to_map);
bool is_friendly_fire(int cid1, int cid2);
bool is_force_balanced();
/*
*/
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 bool can_spawn(class PLAYER *p, vec2 *pos);
/*
*/
virtual const char *get_team_name(int team);
virtual int get_auto_team(int notthisid);
virtual bool can_join_team(int team, int notthisid);
bool check_team_balance();
bool can_change_team(PLAYER *pplayer, int jointeam);
int clampteam(int team);
virtual void post_reset();
};
#endif

View File

@@ -0,0 +1,228 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <engine/e_server_interface.h>
#include <game/mapitems.hpp>
#include <game/server/entities/character.hpp>
#include <game/server/player.hpp>
#include <game/server/gamecontext.hpp>
#include "ctf.hpp"
GAMECONTROLLER_CTF::GAMECONTROLLER_CTF()
{
flags[0] = 0;
flags[1] = 0;
gametype = "CTF";
game_flags = GAMEFLAG_TEAMS|GAMEFLAG_FLAGS;
}
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 && killer && f->carrying_character == killer->get_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;
// flag hits death-tile, reset it
if(col_get((int)f->pos.x, (int)f->pos.y)&COLFLAG_DEATH)
{
game.create_sound_global(SOUND_CTF_RETURN);
f->reset();
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));
char buf[512];
float capture_time = (server_tick() - f->grab_tick)/(float)server_tickspeed();
if(capture_time <= 60)
{
str_format(buf, sizeof(buf), "the %s flag was captured by %s (%d.%s%d seconds)", fi ? "blue" : "red", server_clientname(f->carrying_character->player->client_id), (int)capture_time%60, ((int)(capture_time*100)%100)<10?"0":"", (int)(capture_time*100)%100);
}
else
{
str_format(buf, sizeof(buf), "the %s flag was captured by %s", fi ? "blue" : "red", server_clientname(f->carrying_character->player->client_id));
}
game.send_chat(-1, -2, buf);
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]->alive || close_characters[i]->player->team == -1 || col_intersect_line(f->pos, close_characters[i]->pos, NULL, NULL))
continue;
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->grab_tick = server_tick();
}
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])
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;
grab_tick = 0;
reset();
// TODO: should this be done here?
game.world.insert_entity(this);
}
void FLAG::reset()
{
carrying_character = 0x0;
at_stand = 1;
pos = stand_pos;
vel = vec2(0,0);
grab_tick = 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 && carrying_character->player)
flag->carried_by = carrying_character->player->client_id;
}

View File

@@ -0,0 +1,36 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <game/server/gamecontroller.hpp>
#include <game/server/entity.hpp>
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;
int grab_tick;
FLAG(int _team);
virtual void reset();
virtual void snap(int snapping_client);
};

View File

@@ -0,0 +1,14 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include "dm.hpp"
GAMECONTROLLER_DM::GAMECONTROLLER_DM()
{
gametype = "DM";
}
void GAMECONTROLLER_DM::tick()
{
do_player_score_wincheck();
GAMECONTROLLER::tick();
}

View File

@@ -0,0 +1,10 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <game/server/gamecontroller.hpp>
class GAMECONTROLLER_DM : public GAMECONTROLLER
{
public:
GAMECONTROLLER_DM();
virtual void tick();
};

View File

@@ -0,0 +1,20 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include "mod.hpp"
GAMECONTROLLER_MOD::GAMECONTROLLER_MOD()
{
// Exchange this to a string that identifies your game mode.
// DM, TDM and CTF are reserved for teeworlds original modes.
gametype = "MOD";
//game_flags = GAMEFLAG_TEAMS; // GAMEFLAG_TEAMS makes it a two-team gamemode
}
void GAMECONTROLLER_MOD::tick()
{
// this is the main part of the gamemode, this function is run every tick
do_player_score_wincheck(); // checks for winners, no teams version
//do_team_score_wincheck(); // checks for winners, two teams version
GAMECONTROLLER::tick();
}

View File

@@ -0,0 +1,13 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <game/server/gamecontroller.hpp>
// you can subclass GAMECONTROLLER_CTF, GAMECONTROLLER_TDM etc if you want
// todo a modification with their base as well.
class GAMECONTROLLER_MOD : public GAMECONTROLLER
{
public:
GAMECONTROLLER_MOD();
virtual void tick();
// add more virtual functions here if you wish
};

View File

@@ -0,0 +1,34 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <engine/e_server_interface.h>
#include <game/server/entities/character.hpp>
#include <game/server/player.hpp>
#include "tdm.hpp"
GAMECONTROLLER_TDM::GAMECONTROLLER_TDM()
{
gametype = "TDM";
game_flags = GAMEFLAG_TEAMS;
}
int GAMECONTROLLER_TDM::on_character_death(class CHARACTER *victim, class PLAYER *killer, int weapon)
{
GAMECONTROLLER::on_character_death(victim, killer, weapon);
if(weapon != WEAPON_GAME)
{
// do team scoring
if(killer == victim->player || killer->team == victim->player->team)
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();
}

View File

@@ -0,0 +1,12 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <game/server/gamecontroller.hpp>
class GAMECONTROLLER_TDM : public GAMECONTROLLER
{
public:
GAMECONTROLLER_TDM();
int on_character_death(class CHARACTER *victim, class PLAYER *killer, int weapon);
virtual void tick();
};

View File

@@ -0,0 +1,217 @@
#include "gameworld.hpp"
#include "entity.hpp"
#include "gamecontext.hpp"
//////////////////////////////////////////////////
// game world
//////////////////////////////////////////////////
GAMEWORLD::GAMEWORLD()
{
paused = false;
reset_requested = false;
first_entity = 0x0;
for(int i = 0; i < NUM_ENT_TYPES; i++)
first_entity_types[i] = 0;
}
GAMEWORLD::~GAMEWORLD()
{
// delete all entities
while(first_entity)
delete first_entity;
}
ENTITY *GAMEWORLD::find_first(int type)
{
return first_entity_types[type];
}
int GAMEWORLD::find_entities(vec2 pos, float radius, ENTITY **ents, int max, int type)
{
int num = 0;
for(ENTITY *ent = (type<0) ? first_entity : first_entity_types[type];
ent; ent = (type<0) ? ent->next_entity : ent->next_type_entity)
{
if(distance(ent->pos, pos) < radius+ent->proximity_radius)
{
ents[num] = ent;
num++;
if(num == max)
break;
}
}
return num;
}
void GAMEWORLD::insert_entity(ENTITY *ent)
{
ENTITY *cur = first_entity;
while(cur)
{
dbg_assert(cur != ent, "err");
cur = cur->next_entity;
}
// insert it
if(first_entity)
first_entity->prev_entity = ent;
ent->next_entity = first_entity;
ent->prev_entity = 0x0;
first_entity = ent;
// into typelist aswell
if(first_entity_types[ent->objtype])
first_entity_types[ent->objtype]->prev_type_entity = ent;
ent->next_type_entity = first_entity_types[ent->objtype];
ent->prev_type_entity = 0x0;
first_entity_types[ent->objtype] = ent;
}
void GAMEWORLD::destroy_entity(ENTITY *ent)
{
ent->marked_for_destroy = true;
}
void GAMEWORLD::remove_entity(ENTITY *ent)
{
// not in the list
if(!ent->next_entity && !ent->prev_entity && first_entity != ent)
return;
// remove
if(ent->prev_entity)
ent->prev_entity->next_entity = ent->next_entity;
else
first_entity = ent->next_entity;
if(ent->next_entity)
ent->next_entity->prev_entity = ent->prev_entity;
if(ent->prev_type_entity)
ent->prev_type_entity->next_type_entity = ent->next_type_entity;
else
first_entity_types[ent->objtype] = ent->next_type_entity;
if(ent->next_type_entity)
ent->next_type_entity->prev_type_entity = ent->prev_type_entity;
ent->next_entity = 0;
ent->prev_entity = 0;
ent->next_type_entity = 0;
ent->prev_type_entity = 0;
}
//
void GAMEWORLD::snap(int snapping_client)
{
for(ENTITY *ent = first_entity; ent; ent = ent->next_entity)
ent->snap(snapping_client);
}
void GAMEWORLD::reset()
{
// reset all entities
for(ENTITY *ent = first_entity; ent; ent = ent->next_entity)
ent->reset();
remove_entities();
game.controller->post_reset();
remove_entities();
reset_requested = false;
}
void GAMEWORLD::remove_entities()
{
// destroy objects marked for destruction
ENTITY *ent = first_entity;
while(ent)
{
ENTITY *next = ent->next_entity;
if(ent->marked_for_destroy)
{
remove_entity(ent);
ent->destroy();
}
ent = next;
}
}
void GAMEWORLD::tick()
{
if(reset_requested)
reset();
if(!paused)
{
if(game.controller->is_force_balanced())
game.send_chat(-1, GAMECONTEXT::CHAT_ALL, "Teams have been balanced");
// update all objects
for(ENTITY *ent = first_entity; ent; ent = ent->next_entity)
ent->tick();
for(ENTITY *ent = first_entity; ent; ent = ent->next_entity)
ent->tick_defered();
}
remove_entities();
}
// TODO: should be more general
CHARACTER *GAMEWORLD::intersect_character(vec2 pos0, vec2 pos1, float radius, vec2& new_pos, ENTITY *notthis)
{
// Find other players
float closest_len = distance(pos0, pos1) * 100.0f;
vec2 line_dir = normalize(pos1-pos0);
CHARACTER *closest = 0;
CHARACTER *p = (CHARACTER *)game.world.find_first(NETOBJTYPE_CHARACTER);
for(; p; p = (CHARACTER *)p->typenext())
{
if(p == notthis)
continue;
vec2 intersect_pos = closest_point_on_line(pos0, pos1, p->pos);
float len = distance(p->pos, intersect_pos);
if(len < CHARACTER::phys_size+radius)
{
if(len < closest_len)
{
new_pos = intersect_pos;
closest_len = len;
closest = p;
}
}
}
return closest;
}
CHARACTER *GAMEWORLD::closest_character(vec2 pos, float radius, ENTITY *notthis)
{
// Find other players
float closest_range = radius*2;
CHARACTER *closest = 0;
CHARACTER *p = (CHARACTER *)game.world.find_first(NETOBJTYPE_CHARACTER);
for(; p; p = (CHARACTER *)p->typenext())
{
if(p == notthis)
continue;
float len = distance(pos, p->pos);
if(len < CHARACTER::phys_size+radius)
{
if(len < closest_range)
{
closest_range = len;
closest = p;
}
}
}
return closest;
}

View File

@@ -0,0 +1,133 @@
#ifndef GAME_SERVER_GAMEWORLD_H
#define GAME_SERVER_GAMEWORLD_H
#include <game/gamecore.hpp>
class ENTITY;
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();
};
#endif

View File

@@ -0,0 +1,601 @@
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <base/math.hpp>
#include <engine/e_config.h>
#include <engine/e_server_interface.h>
extern "C"
{
#include <engine/e_memheap.h>
}
#include <game/version.hpp>
#include <game/collision.hpp>
#include <game/layers.hpp>
#include <game/gamecore.hpp>
#include "gamecontext.hpp"
#include "gamemodes/dm.hpp"
#include "gamemodes/tdm.hpp"
#include "gamemodes/ctf.hpp"
#include "gamemodes/mod.hpp"
TUNING_PARAMS tuning;
static void check_pure_tuning()
{
// might not be created yet during start up
if(!game.controller)
return;
if( strcmp(game.controller->gametype, "DM")==0 ||
strcmp(game.controller->gametype, "TDM")==0 ||
strcmp(game.controller->gametype, "CTF")==0)
{
TUNING_PARAMS p;
if(memcmp(&p, &tuning, sizeof(TUNING_PARAMS)) != 0)
{
dbg_msg("server", "resetting tuning due to pure server");
tuning = p;
}
}
}
struct VOTEOPTION
{
VOTEOPTION *next;
VOTEOPTION *prev;
char command[1];
};
static HEAP *voteoption_heap = 0;
static VOTEOPTION *voteoption_first = 0;
static VOTEOPTION *voteoption_last = 0;
void send_tuning_params(int cid)
{
check_pure_tuning();
msg_pack_start(NETMSGTYPE_SV_TUNEPARAMS, MSGFLAG_VITAL);
int *params = (int *)&tuning;
for(unsigned i = 0; i < sizeof(tuning)/sizeof(int); i++)
msg_pack_int(params[i]);
msg_pack_end();
server_send_msg(cid);
}
// Server hooks
void mods_client_direct_input(int client_id, void *input)
{
if(!game.world.paused)
game.players[client_id]->on_direct_input((NETOBJ_PLAYER_INPUT *)input);
}
void mods_client_predicted_input(int client_id, void *input)
{
if(!game.world.paused)
game.players[client_id]->on_predicted_input((NETOBJ_PLAYER_INPUT *)input);
}
// Server hooks
void mods_tick()
{
check_pure_tuning();
game.tick();
#ifdef CONF_DEBUG
if(config.dbg_dummies)
{
for(int i = 0; i < config.dbg_dummies ; i++)
{
NETOBJ_PLAYER_INPUT input = {0};
input.direction = (i&1)?-1:1;
game.players[MAX_CLIENTS-i-1]->on_predicted_input(&input);
}
}
#endif
}
void mods_snap(int client_id)
{
game.snap(client_id);
}
void mods_client_enter(int client_id)
{
//game.world.insert_entity(&game.players[client_id]);
game.players[client_id]->respawn();
dbg_msg("game", "join player='%d:%s'", client_id, server_clientname(client_id));
char buf[512];
str_format(buf, sizeof(buf), "%s entered and joined the %s", server_clientname(client_id), game.controller->get_team_name(game.players[client_id]->team));
game.send_chat(-1, GAMECONTEXT::CHAT_ALL, buf);
dbg_msg("game", "team_join player='%d:%s' team=%d", client_id, server_clientname(client_id), game.players[client_id]->team);
}
void mods_connected(int client_id)
{
game.players[client_id] = new(client_id) PLAYER(client_id);
//game.players[client_id].init(client_id);
//game.players[client_id].client_id = client_id;
// Check which team the player should be on
if(config.sv_tournament_mode)
game.players[client_id]->team = -1;
else
game.players[client_id]->team = game.controller->get_auto_team(client_id);
(void) game.controller->check_team_balance();
// send motd
NETMSG_SV_MOTD msg;
msg.message = config.sv_motd;
msg.pack(MSGFLAG_VITAL);
server_send_msg(client_id);
}
void mods_client_drop(int client_id)
{
game.abort_vote_kick_on_disconnect(client_id);
game.players[client_id]->on_disconnect();
delete game.players[client_id];
game.players[client_id] = 0;
(void) game.controller->check_team_balance();
}
/*static bool is_separator(char c) { return c == ';' || c == ' ' || c == ',' || c == '\t'; }
static const char *liststr_find(const char *str, const char *needle)
{
int needle_len = strlen(needle);
while(*str)
{
int wordlen = 0;
while(str[wordlen] && !is_separator(str[wordlen]))
wordlen++;
if(wordlen == needle_len && strncmp(str, needle, needle_len) == 0)
return str;
str += wordlen+1;
}
return 0;
}*/
void mods_message(int msgtype, int client_id)
{
void *rawmsg = netmsg_secure_unpack(msgtype);
PLAYER *p = game.players[client_id];
if(!rawmsg)
{
dbg_msg("server", "dropped weird message '%s' (%d), failed on '%s'", netmsg_get_name(msgtype), msgtype, netmsg_failed_on());
return;
}
if(msgtype == NETMSGTYPE_CL_SAY)
{
NETMSG_CL_SAY *msg = (NETMSG_CL_SAY *)rawmsg;
int team = msg->team;
if(team)
team = p->team;
else
team = GAMECONTEXT::CHAT_ALL;
if(config.sv_spamprotection && p->last_chat+time_freq() > time_get())
return;
p->last_chat = time_get();
game.send_chat(client_id, team, msg->message);
}
else if(msgtype == NETMSGTYPE_CL_CALLVOTE)
{
int64 now = time_get();
if(game.vote_closetime)
{
game.send_chat_target(client_id, "Wait for current vote to end before calling a new one.");
return;
}
int64 timeleft = p->last_votecall + time_freq()*60 - now;
if(timeleft > 0)
{
char chatmsg[512] = {0};
str_format(chatmsg, sizeof(chatmsg), "You must wait %d seconds before making another vote", (timeleft/time_freq())+1);
game.send_chat_target(client_id, chatmsg);
return;
}
char chatmsg[512] = {0};
char desc[512] = {0};
char cmd[512] = {0};
NETMSG_CL_CALLVOTE *msg = (NETMSG_CL_CALLVOTE *)rawmsg;
if(str_comp_nocase(msg->type, "option") == 0)
{
VOTEOPTION *option = voteoption_first;
while(option)
{
if(str_comp_nocase(msg->value, option->command) == 0)
{
str_format(chatmsg, sizeof(chatmsg), "%s called vote to change server option '%s'", server_clientname(client_id), option->command);
str_format(desc, sizeof(desc), "%s", option->command);
str_format(cmd, sizeof(cmd), "%s", option->command);
break;
}
option = option->next;
}
if(!option)
{
str_format(chatmsg, sizeof(chatmsg), "'%s' isn't an option on this server", msg->value);
game.send_chat_target(client_id, chatmsg);
return;
}
}
else if(str_comp_nocase(msg->type, "kick") == 0)
{
if(!config.sv_vote_kick)
{
game.send_chat_target(client_id, "Server does not allow voting to kick players");
return;
}
int kick_id = atoi(msg->value);
if(kick_id < 0 || kick_id >= MAX_CLIENTS || !game.players[kick_id])
{
game.send_chat_target(client_id, "Invalid client id to kick");
return;
}
str_format(chatmsg, sizeof(chatmsg), "Vote called to kick '%s'", server_clientname(kick_id));
str_format(desc, sizeof(desc), "Kick '%s'", server_clientname(kick_id));
str_format(cmd, sizeof(cmd), "kick %d", kick_id);
if (!config.sv_vote_kick_bantime)
str_format(cmd, sizeof(cmd), "kick %d", kick_id);
else
str_format(cmd, sizeof(cmd), "ban %d %d", kick_id, config.sv_vote_kick_bantime);
}
if(cmd[0])
{
game.send_chat(-1, GAMECONTEXT::CHAT_ALL, chatmsg);
game.start_vote(desc, cmd);
p->vote = 1;
game.vote_creator = client_id;
p->last_votecall = now;
game.send_vote_status(-1);
}
}
else if(msgtype == NETMSGTYPE_CL_VOTE)
{
if(!game.vote_closetime)
return;
if(p->vote == 0)
{
NETMSG_CL_VOTE *msg = (NETMSG_CL_VOTE *)rawmsg;
p->vote = msg->vote;
game.send_vote_status(-1);
}
}
else if (msgtype == NETMSGTYPE_CL_SETTEAM && !game.world.paused)
{
NETMSG_CL_SETTEAM *msg = (NETMSG_CL_SETTEAM *)rawmsg;
if(p->team == msg->team || (config.sv_spamprotection && p->last_setteam+time_freq()*3 > time_get()))
return;
// Switch team on given client and kill/respawn him
if(game.controller->can_join_team(msg->team, client_id))
{
if(game.controller->can_change_team(p, msg->team))
{
p->last_setteam = time_get();
p->set_team(msg->team);
(void) game.controller->check_team_balance();
}
else
game.send_broadcast("Teams must be balanced, please join other team", client_id);
}
else
{
char buf[128];
str_format(buf, sizeof(buf), "Only %d active players are allowed", config.sv_max_clients-config.sv_spectator_slots);
game.send_broadcast(buf, client_id);
}
}
else if (msgtype == NETMSGTYPE_CL_CHANGEINFO || msgtype == NETMSGTYPE_CL_STARTINFO)
{
NETMSG_CL_CHANGEINFO *msg = (NETMSG_CL_CHANGEINFO *)rawmsg;
if(config.sv_spamprotection && p->last_changeinfo+time_freq()*5 > time_get())
return;
p->last_changeinfo = time_get();
p->use_custom_color = msg->use_custom_color;
p->color_body = msg->color_body;
p->color_feet = msg->color_feet;
// check for invalid chars
unsigned char *name = (unsigned char *)msg->name;
while (*name)
{
if(*name < 32)
*name = ' ';
name++;
}
// copy old name
char oldname[MAX_NAME_LENGTH];
str_copy(oldname, server_clientname(client_id), MAX_NAME_LENGTH);
server_setclientname(client_id, msg->name);
if(msgtype == NETMSGTYPE_CL_CHANGEINFO && strcmp(oldname, server_clientname(client_id)) != 0)
{
char chattext[256];
str_format(chattext, sizeof(chattext), "%s changed name to %s", oldname, server_clientname(client_id));
game.send_chat(-1, GAMECONTEXT::CHAT_ALL, chattext);
}
// set skin
str_copy(p->skin_name, msg->skin, sizeof(p->skin_name));
game.controller->on_player_info_change(p);
if(msgtype == NETMSGTYPE_CL_STARTINFO)
{
// send vote options
NETMSG_SV_VOTE_CLEAROPTIONS clearmsg;
clearmsg.pack(MSGFLAG_VITAL);
server_send_msg(client_id);
VOTEOPTION *current = voteoption_first;
while(current)
{
NETMSG_SV_VOTE_OPTION optionmsg;
optionmsg.command = current->command;
optionmsg.pack(MSGFLAG_VITAL);
server_send_msg(client_id);
current = current->next;
}
// send tuning parameters to client
send_tuning_params(client_id);
//
NETMSG_SV_READYTOENTER m;
m.pack(MSGFLAG_VITAL|MSGFLAG_FLUSH);
server_send_msg(client_id);
}
}
else if (msgtype == NETMSGTYPE_CL_EMOTICON && !game.world.paused)
{
NETMSG_CL_EMOTICON *msg = (NETMSG_CL_EMOTICON *)rawmsg;
if(config.sv_spamprotection && p->last_emote+time_freq()*3 > time_get())
return;
p->last_emote = time_get();
game.send_emoticon(client_id, msg->emoticon);
}
else if (msgtype == NETMSGTYPE_CL_KILL && !game.world.paused)
{
if(p->last_kill+time_freq()*3 > time_get())
return;
p->last_kill = time_get();
p->kill_character(WEAPON_SELF);
p->respawn_tick = server_tick()+server_tickspeed()*3;
}
}
static void con_tune_param(void *result, void *user_data)
{
const char *param_name = console_arg_string(result, 0);
float new_value = console_arg_float(result, 1);
if(tuning.set(param_name, new_value))
{
dbg_msg("tuning", "%s changed to %.2f", param_name, new_value);
send_tuning_params(-1);
}
else
console_print("No such tuning parameter");
}
static void con_tune_reset(void *result, void *user_data)
{
TUNING_PARAMS p;
tuning = p;
send_tuning_params(-1);
console_print("tuning reset");
}
static void con_tune_dump(void *result, void *user_data)
{
for(int i = 0; i < tuning.num(); i++)
{
float v;
tuning.get(i, &v);
dbg_msg("tuning", "%s %.2f", tuning.names[i], v);
}
}
static void con_change_map(void *result, void *user_data)
{
game.controller->change_map(console_arg_string(result, 0));
}
static void con_restart(void *result, void *user_data)
{
if(console_arg_num(result))
game.controller->do_warmup(console_arg_int(result, 0));
else
game.controller->startround();
}
static void con_broadcast(void *result, void *user_data)
{
game.send_broadcast(console_arg_string(result, 0), -1);
}
static void con_say(void *result, void *user_data)
{
game.send_chat(-1, GAMECONTEXT::CHAT_ALL, console_arg_string(result, 0));
}
static void con_set_team(void *result, void *user_data)
{
int client_id = clamp(console_arg_int(result, 0), 0, (int)MAX_CLIENTS - 1);
int team = clamp(console_arg_int(result, 1), -1, 1);
dbg_msg("", "%d %d", client_id, team);
if(!game.players[client_id])
return;
game.players[client_id]->set_team(team);
(void) game.controller->check_team_balance();
}
static void con_addvote(void *result, void *user_data)
{
int len = strlen(console_arg_string(result, 0));
if(!voteoption_heap)
voteoption_heap = memheap_create();
VOTEOPTION *option = (VOTEOPTION *)memheap_allocate(voteoption_heap, sizeof(VOTEOPTION) + len);
option->next = 0;
option->prev = voteoption_last;
if(option->prev)
option->prev->next = option;
voteoption_last = option;
if(!voteoption_first)
voteoption_first = option;
mem_copy(option->command, console_arg_string(result, 0), len+1);
dbg_msg("server", "added option '%s'", option->command);
}
static void con_vote(void *result, void *user_data)
{
if(str_comp_nocase(console_arg_string(result, 0), "yes") == 0)
game.vote_enforce = GAMECONTEXT::VOTE_ENFORCE_YES;
else if(str_comp_nocase(console_arg_string(result, 0), "no") == 0)
game.vote_enforce = GAMECONTEXT::VOTE_ENFORCE_NO;
dbg_msg("server", "forcing vote %s", console_arg_string(result, 0));
}
void mods_console_init()
{
MACRO_REGISTER_COMMAND("tune", "si", CFGFLAG_SERVER, con_tune_param, 0, "");
MACRO_REGISTER_COMMAND("tune_reset", "", CFGFLAG_SERVER, con_tune_reset, 0, "");
MACRO_REGISTER_COMMAND("tune_dump", "", CFGFLAG_SERVER, con_tune_dump, 0, "");
MACRO_REGISTER_COMMAND("change_map", "r", CFGFLAG_SERVER, con_change_map, 0, "");
MACRO_REGISTER_COMMAND("restart", "?i", CFGFLAG_SERVER, con_restart, 0, "");
MACRO_REGISTER_COMMAND("broadcast", "r", CFGFLAG_SERVER, con_broadcast, 0, "");
MACRO_REGISTER_COMMAND("say", "r", CFGFLAG_SERVER, con_say, 0, "");
MACRO_REGISTER_COMMAND("set_team", "ii", CFGFLAG_SERVER, con_set_team, 0, "");
MACRO_REGISTER_COMMAND("addvote", "r", CFGFLAG_SERVER, con_addvote, 0, "");
MACRO_REGISTER_COMMAND("vote", "r", CFGFLAG_SERVER, con_vote, 0, "");
}
void mods_init()
{
//if(!data) /* only load once */
//data = load_data_from_memory(internal_data);
for(int i = 0; i < NUM_NETOBJTYPES; i++)
snap_set_staticsize(i, netobj_get_size(i));
layers_init();
col_init();
// reset everything here
//world = new GAMEWORLD;
//players = new PLAYER[MAX_CLIENTS];
// select gametype
if(strcmp(config.sv_gametype, "mod") == 0)
game.controller = new GAMECONTROLLER_MOD;
else if(strcmp(config.sv_gametype, "ctf") == 0)
game.controller = new GAMECONTROLLER_CTF;
else if(strcmp(config.sv_gametype, "tdm") == 0)
game.controller = new GAMECONTROLLER_TDM;
else
game.controller = new GAMECONTROLLER_DM;
// setup core world
//for(int i = 0; i < MAX_CLIENTS; i++)
// game.players[i].core.world = &game.world.core;
// create all entities from the game layer
MAPITEM_LAYER_TILEMAP *tmap = layers_game_layer();
TILE *tiles = (TILE *)map_get_data(tmap->data);
/*
num_spawn_points[0] = 0;
num_spawn_points[1] = 0;
num_spawn_points[2] = 0;
*/
for(int y = 0; y < tmap->height; y++)
{
for(int x = 0; x < tmap->width; x++)
{
int index = tiles[y*tmap->width+x].index;
if(index >= ENTITY_OFFSET)
{
vec2 pos(x*32.0f+16.0f, y*32.0f+16.0f);
game.controller->on_entity(index-ENTITY_OFFSET, pos);
}
}
}
//game.world.insert_entity(game.controller);
#ifdef CONF_DEBUG
if(config.dbg_dummies)
{
for(int i = 0; i < config.dbg_dummies ; i++)
{
mods_connected(MAX_CLIENTS-i-1);
mods_client_enter(MAX_CLIENTS-i-1);
if(game.controller->is_teamplay())
game.players[MAX_CLIENTS-i-1]->team = i&1;
}
}
#endif
}
void mods_shutdown()
{
delete game.controller;
game.controller = 0;
game.clear();
}
void mods_presnap() {}
void mods_postsnap()
{
game.events.clear();
}
extern "C" const char *mods_net_version() { return GAME_NETVERSION; }
extern "C" const char *mods_version() { return GAME_VERSION; }

View File

@@ -0,0 +1,183 @@
#include <new>
#include <engine/e_server_interface.h>
#include "player.hpp"
#include "gamecontext.hpp"
MACRO_ALLOC_POOL_ID_IMPL(PLAYER, MAX_CLIENTS)
PLAYER::PLAYER(int client_id)
{
respawn_tick = server_tick();
character = 0;
this->client_id = client_id;
}
PLAYER::~PLAYER()
{
delete character;
character = 0;
}
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(!character && die_tick+server_tickspeed()*3 <= server_tick())
spawning = true;
if(character)
{
if(character->alive)
{
view_pos = character->pos;
}
else
{
delete character;
character = 0;
}
}
else if(spawning && respawn_tick <= server_tick())
try_respawn();
}
void PLAYER::snap(int snapping_client)
{
NETOBJ_CLIENT_INFO *client_info = (NETOBJ_CLIENT_INFO *)snap_new_item(NETOBJTYPE_CLIENT_INFO, client_id, sizeof(NETOBJ_CLIENT_INFO));
str_to_ints(&client_info->name0, 6, server_clientname(client_id));
str_to_ints(&client_info->skin0, 6, skin_name);
client_info->use_custom_color = use_custom_color;
client_info->color_body = color_body;
client_info->color_feet = color_feet;
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 == snapping_client)
info->local = 1;
}
void PLAYER::on_disconnect()
{
kill_character(WEAPON_GAME);
//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, GAMECONTEXT::CHAT_ALL, buf);
dbg_msg("game", "leave player='%d:%s'", client_id, server_clientname(client_id));
}
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;
if(!chr && team == -1)
view_pos = vec2(new_input->target_x, new_input->target_y);
}
CHARACTER *PLAYER::get_character()
{
if(character && character->alive)
return character;
return 0;
}
void PLAYER::kill_character(int weapon)
{
//CHARACTER *chr = get_character();
if(character)
{
character->die(client_id, weapon);
delete character;
character = 0;
}
}
void PLAYER::respawn()
{
if(team > -1)
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, GAMECONTEXT::CHAT_ALL, buf);
kill_character(WEAPON_GAME);
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]);
}
void PLAYER::try_respawn()
{
vec2 spawnpos = vec2(100.0f, -60.0f);
if(!game.controller->can_spawn(this, &spawnpos))
return;
// 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 = new(client_id) CHARACTER();
character->spawn(this, spawnpos, team);
game.create_playerspawn(spawnpos);
}
}

View File

@@ -0,0 +1,75 @@
#ifndef GAME_SERVER_PLAYER_H
#define GAME_SERVER_PLAYER_H
// this include should perhaps be removed
#include "entities/character.hpp"
// player object
class PLAYER
{
MACRO_ALLOC_POOL_ID()
private:
CHARACTER *character;
public:
PLAYER(int client_id);
~PLAYER();
// TODO: clean this up
char skin_name[64];
int use_custom_color;
int color_body;
int color_feet;
int respawn_tick;
int die_tick;
//
bool spawning;
int client_id;
int team;
int score;
bool force_balanced;
//
int vote;
int64 last_votecall;
//
int64 last_chat;
int64 last_setteam;
int64 last_changeinfo;
int64 last_emote;
int64 last_kill;
// network latency calculations
struct
{
int accum;
int accum_min;
int accum_max;
int avg;
int min;
int max;
} latency;
// 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(int weapon);
void try_respawn();
void respawn();
void set_team(int team);
void tick();
void snap(int snapping_client);
void on_direct_input(NETOBJ_PLAYER_INPUT *new_input);
void on_predicted_input(NETOBJ_PLAYER_INPUT *new_input);
void on_disconnect();
};
#endif