Added TeeWorlds - it compiles but does not work yet (crashed my Evo yay!)
This commit is contained in:
@@ -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(¤t, sizeof(current));
|
||||
reckoningcore.write(&predicted);
|
||||
core.write(¤t);
|
||||
|
||||
// only allow dead reackoning for a top of 3 seconds
|
||||
if(reckoning_tick+server_tickspeed()*3 < server_tick() || mem_comp(&predicted, ¤t, 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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
49
project/jni/application/teeworlds/src/game/server/entity.cpp
Normal file
49
project/jni/application/teeworlds/src/game/server/entity.cpp
Normal 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;
|
||||
}
|
||||
145
project/jni/application/teeworlds/src/game/server/entity.hpp
Normal file
145
project/jni/application/teeworlds/src/game/server/entity.hpp
Normal 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
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
217
project/jni/application/teeworlds/src/game/server/gameworld.cpp
Normal file
217
project/jni/application/teeworlds/src/game/server/gameworld.cpp
Normal 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;
|
||||
}
|
||||
133
project/jni/application/teeworlds/src/game/server/gameworld.hpp
Normal file
133
project/jni/application/teeworlds/src/game/server/gameworld.hpp
Normal 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
|
||||
601
project/jni/application/teeworlds/src/game/server/hooks.cpp
Normal file
601
project/jni/application/teeworlds/src/game/server/hooks.cpp
Normal 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; }
|
||||
183
project/jni/application/teeworlds/src/game/server/player.cpp
Normal file
183
project/jni/application/teeworlds/src/game/server/player.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
75
project/jni/application/teeworlds/src/game/server/player.hpp
Normal file
75
project/jni/application/teeworlds/src/game/server/player.hpp
Normal 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
|
||||
Reference in New Issue
Block a user