ddnet/src/game/server/gamecontroller.cpp

634 lines
13 KiB
C++
Raw Normal View History

2007-11-25 19:42:40 +00:00
/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
2008-01-13 11:43:43 +00:00
#include <string.h>
2007-12-15 10:24:49 +00:00
#include <engine/e_config.h>
#include <engine/e_server_interface.h>
2008-08-17 08:52:24 +00:00
#include <game/mapitems.hpp>
#include <game/generated/g_protocol.hpp>
#include "entities/pickup.hpp"
#include "gamecontroller.hpp"
#include "gamecontext.hpp"
2008-08-27 20:04:07 +00:00
GAMECONTROLLER::GAMECONTROLLER()
{
gametype = "unknown";
2008-08-31 14:37:35 +00:00
2008-08-27 20:04:07 +00:00
//
do_warmup(config.sv_warmup);
game_over_tick = -1;
sudden_death = 0;
round_start_tick = server_tick();
round_count = 0;
2008-08-31 14:37:35 +00:00
game_flags = 0;
2008-08-27 20:04:07 +00:00
teamscore[0] = 0;
teamscore[1] = 0;
unbalanced_tick = -1;
force_balanced = false;
2008-08-27 20:04:07 +00:00
num_spawn_points[0] = 0;
num_spawn_points[1] = 0;
num_spawn_points[2] = 0;
}
2008-10-02 14:44:35 +00:00
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;
2008-08-31 14:37:35 +00:00
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;
2008-08-27 20:04:07 +00:00
return eval.got;
}
2008-08-27 20:04:07 +00:00
bool GAMECONTROLLER::on_entity(int index, vec2 pos)
2008-01-13 11:43:43 +00:00
{
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;
}
2008-02-02 18:05:16 +00:00
else if(index == ENTITY_WEAPON_GRENADE)
2008-01-13 11:43:43 +00:00
{
type = POWERUP_WEAPON;
2008-02-02 18:05:16 +00:00
subtype = WEAPON_GRENADE;
2008-01-13 11:43:43 +00:00
}
else if(index == ENTITY_WEAPON_RIFLE)
{
type = POWERUP_WEAPON;
subtype = WEAPON_RIFLE;
}
else if(index == ENTITY_POWERUP_NINJA && config.sv_powerups)
2008-01-13 11:43:43 +00:00
{
type = POWERUP_NINJA;
subtype = WEAPON_NINJA;
}
if(type != -1)
{
PICKUP *pickup = new PICKUP(type, subtype);
pickup->pos = pos;
2008-01-13 11:43:43 +00:00
return true;
}
return false;
}
void GAMECONTROLLER::endround()
{
2007-10-06 17:36:24 +00:00
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)
{
2008-08-31 14:37:35 +00:00
if(is_teamplay())
{
if(team == 0)
return "red team";
else if(team == 1)
return "blue team";
}
else
{
if(team == 0)
return "game";
}
return "spectators";
}
2008-01-12 12:27:55 +00:00
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;
round_count++;
}
void GAMECONTROLLER::cyclemap()
{
if(!strlen(config.sv_maprotation))
return;
if(round_count < config.sv_rounds_per_map-1)
return;
// handle maprotation
const char *map_rotation = config.sv_maprotation;
const char *current_map = config.sv_map;
int current_map_len = strlen(current_map);
const char *next_map = map_rotation;
while(*next_map)
{
int wordlen = 0;
while(next_map[wordlen] && !is_separator(next_map[wordlen]))
wordlen++;
if(wordlen == current_map_len && strncmp(next_map, current_map, current_map_len) == 0)
{
// map found
next_map += current_map_len;
while(*next_map && is_separator(*next_map))
next_map++;
break;
}
next_map++;
}
// restart rotation
if(next_map[0] == 0)
next_map = map_rotation;
// cut out the next map
char buf[512];
for(int i = 0; i < 512; i++)
{
buf[i] = next_map[i];
if(is_separator(next_map[i]) || next_map[i] == 0)
{
buf[i] = 0;
break;
}
}
// skip spaces
int i = 0;
while(is_separator(buf[i]))
i++;
dbg_msg("game", "rotating map to %s", &buf[i]);
str_copy(config.sv_map, &buf[i], sizeof(config.sv_map));
}
void GAMECONTROLLER::post_reset()
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(game.players[i])
game.players[i]->respawn();
}
}
void GAMECONTROLLER::on_player_info_change(class PLAYER *p)
{
2007-11-26 22:26:33 +00:00
const int team_colors[2] = {65387, 10223467};
2008-08-31 14:37:35 +00:00
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
2007-12-11 21:19:52 +00:00
{
2008-08-31 14:37:35 +00:00
if(is_teamplay() && victim->team == killer->team)
2007-12-11 21:19:52 +00:00
killer->score--; // teamkill
else
killer->score++; // normal kill
}
return 0;
}
2008-08-27 20:04:07 +00:00
void GAMECONTROLLER::on_character_spawn(class CHARACTER *chr)
{
// default health
chr->health = 10;
2008-08-27 20:04:07 +00:00
// 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)
2007-10-06 17:36:24 +00:00
{
warmup = seconds*server_tickspeed();
2007-10-06 17:36:24 +00:00
}
bool GAMECONTROLLER::is_friendly_fire(int cid1, int cid2)
2007-12-10 20:53:37 +00:00
{
if(cid1 == cid2)
return false;
2008-08-31 14:37:35 +00:00
if(is_teamplay())
2007-12-10 20:53:37 +00:00
{
if(game.players[cid1]->team == game.players[cid2]->team)
2007-12-10 20:53:37 +00:00
return true;
}
return false;
}
bool GAMECONTROLLER::is_force_balanced()
{
if(force_balanced)
{
force_balanced = false;
return true;
}
else
return false;
}
void GAMECONTROLLER::tick()
{
2007-10-06 17:36:24 +00:00
// do warmup
if(warmup)
{
warmup--;
if(!warmup)
2007-12-10 21:05:57 +00:00
startround();
2007-10-06 17:36:24 +00:00
}
if(game_over_tick != -1)
{
// game over.. wait for restart
if(server_tick() > game_over_tick+server_tickspeed()*10)
{
cyclemap();
startround();
}
}
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;
}
}
int m = (t[0] > t[1]) ? 0 : 1;
int num_balance = abs(t[0]-t[1]) / 2;
do
{
// move player who is closest to team-scorediff
PLAYER *p = 0;
int pd = tscore[m];
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(!game.players[i])
continue;
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));
}
}
// 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;
2007-12-11 23:10:07 +00:00
if(config.sv_timelimit > 0)
prog = max(prog, (server_tick()-round_start_tick) * 100 / (config.sv_timelimit*server_tickspeed()*60));
2007-12-11 23:10:07 +00:00
if(config.sv_scorelimit)
{
2008-08-31 14:37:35 +00:00
if(is_teamplay())
{
2007-12-11 23:10:07 +00:00
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);
}
2008-08-31 14:37:35 +00:00
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;
2008-08-31 14:37:35 +00:00
gameobj->flags = game_flags;
gameobj->warmup = warmup;
2007-10-06 17:36:24 +00:00
2008-09-07 15:07:08 +00:00
gameobj->round_num = (strlen(config.sv_maprotation) || config.sv_rounds_per_map > 1) ? 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)
{
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]++;
}
}
2008-03-23 14:59:58 +00:00
int team = 0;
2008-08-31 14:37:35 +00:00
if(is_teamplay())
2008-03-23 14:59:58 +00:00
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)
2008-03-23 14:59:58 +00:00
{
(void)team;
int numplayers[2] = {0,0};
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(game.players[i] && i != notthisid)
2008-03-23 14:59:58 +00:00
{
if(game.players[i]->team >= 0 || game.players[i]->team == 1)
numplayers[game.players[i]->team]++;
2008-03-23 14:59:58 +00:00
}
}
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()
2008-01-17 23:09:49 +00:00
{
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])
2008-01-17 23:09:49 +00:00
{
if(game.players[i]->score > topscore)
2008-01-17 23:09:49 +00:00
{
topscore = game.players[i]->score;
2008-01-17 23:09:49 +00:00
topscore_count = 1;
}
else if(game.players[i]->score == topscore)
2008-01-17 23:09:49 +00:00
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()
2007-11-26 20:47:49 +00:00
{
if(game_over_tick == -1 && !warmup)
{
// check score win condition
2007-12-11 23:10:07 +00:00
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))
2007-11-26 20:47:49 +00:00
{
if(teamscore[0] != teamscore[1])
endround();
else
sudden_death = 1;
}
}
}
int GAMECONTROLLER::clampteam(int team)
{
if(team < 0) // spectator
return -1;
2008-08-31 14:37:35 +00:00
if(is_teamplay())
return team&1;
return 0;
}
GAMECONTROLLER *gamecontroller = 0;