ddnet/src/game/server/game_server.cpp

2002 lines
43 KiB
C++
Raw Normal View History

2007-05-22 15:03:32 +00:00
#include <stdlib.h>
2007-07-30 07:05:34 +00:00
#include <stdio.h>
2007-05-22 15:03:32 +00:00
#include <string.h>
#include <engine/config.h>
#include "../game.h"
2007-07-13 13:40:04 +00:00
#include "data.h"
#include "game_server.h"
2007-05-22 15:03:32 +00:00
2007-07-21 19:03:50 +00:00
data_container *data = 0x0;
2007-05-22 15:03:32 +00:00
using namespace baselib;
2007-07-14 13:09:42 +00:00
// --------- DEBUG STUFF ---------
2007-08-05 10:19:46 +00:00
const int debug_bots = 0;
2007-07-13 13:40:04 +00:00
2007-05-22 15:03:32 +00:00
// --------- PHYSICS TWEAK! --------
const float ground_control_speed = 7.0f;
const float ground_control_accel = 2.0f;
const float ground_friction = 0.5f;
2007-08-01 06:17:18 +00:00
const float ground_jump_speed = 13.5f;
2007-05-22 15:03:32 +00:00
const float air_control_speed = 3.5f;
const float air_control_accel = 1.2f;
const float air_friction = 0.95f;
2007-08-01 06:17:18 +00:00
const float hook_length = 34*10.0f;
2007-05-22 15:03:32 +00:00
const float hook_fire_speed = 45.0f;
const float hook_drag_accel = 3.0f;
const float hook_drag_speed = 15.0f;
const float gravity = 0.5f;
class player* get_player(int index);
void create_damageind(vec2 p, float angle_mod, int amount);
void create_explosion(vec2 p, int owner, int weapon, bool bnodamage);
2007-05-22 15:03:32 +00:00
void create_smoke(vec2 p);
2007-07-21 19:03:50 +00:00
void create_spawn(vec2 p);
2007-07-21 21:17:38 +00:00
void create_death(vec2 p);
2007-05-22 15:03:32 +00:00
void create_sound(vec2 pos, int sound, int loopflags = 0);
2007-08-04 17:28:31 +00:00
void create_targetted_sound(vec2 pos, int sound, int target, int loopflags = 0);
class player *intersect_player(vec2 pos0, vec2 pos1, vec2 &new_pos, class entity *notthis = 0);
2007-05-22 15:03:32 +00:00
2007-07-13 13:40:04 +00:00
template<typename T>
T saturated_add(T min, T max, T current, T modifier)
{
if(modifier < 0)
{
if(current < min)
return current;
current += modifier;
if(current < min)
current = min;
return current;
}
else
{
if(current > max)
return current;
current += modifier;
if(current > max)
current = max;
return current;
}
}
2007-08-04 19:00:06 +00:00
// TODO: rewrite this smarter!
void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity, int *bounces)
{
if(bounces)
*bounces = 0;
vec2 pos = *inout_pos;
vec2 vel = *inout_vel;
if(col_check_point(pos + vel))
{
int affected = 0;
if(col_check_point(pos.x + vel.x, pos.y))
{
inout_vel->x *= -elasticity;
if(bounces)
(*bounces)++;
affected++;
}
if(col_check_point(pos.x, pos.y + vel.y))
{
inout_vel->y *= -elasticity;
if(bounces)
(*bounces)++;
affected++;
}
if(affected == 0)
{
inout_vel->x *= -elasticity;
inout_vel->y *= -elasticity;
}
}
else
{
*inout_pos = pos + vel;
}
}
2007-05-22 15:03:32 +00:00
// TODO: rewrite this smarter!
void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity)
{
// do the move
vec2 pos = *inout_pos;
vec2 vel = *inout_vel;
float distance = length(vel);
int max = (int)distance;
vec2 offsets[4] = { vec2(-size.x/2, -size.y/2), vec2( size.x/2, -size.y/2),
vec2(-size.x/2, size.y/2), vec2( size.x/2, size.y/2)};
if(distance > 0.00001f)
{
vec2 old_pos = pos;
for(int i = 0; i <= max; i++)
{
float amount = i/(float)max;
if(max == 0)
amount = 0;
vec2 new_pos = pos + vel*amount; // TODO: this row is not nice
for(int p = 0; p < 4; p++)
{
vec2 np = new_pos+offsets[p];
vec2 op = old_pos+offsets[p];
if(col_check_point(np))
{
int affected = 0;
if(col_check_point(np.x, op.y))
{
vel.x = -vel.x*elasticity;
pos.x = old_pos.x;
new_pos.x = old_pos.x;
affected++;
}
if(col_check_point(op.x, np.y))
{
vel.y = -vel.y*elasticity;
pos.y = old_pos.y;
new_pos.y = old_pos.y;
affected++;
}
if(!affected)
{
new_pos = old_pos;
pos = old_pos;
vel *= -elasticity;
}
}
}
old_pos = new_pos;
}
pos = old_pos;
}
*inout_pos = pos;
*inout_vel = vel;
}
//////////////////////////////////////////////////
// Event handler
//////////////////////////////////////////////////
event_handler::event_handler()
2007-05-22 15:03:32 +00:00
{
clear();
}
2007-05-22 15:03:32 +00:00
2007-08-04 17:28:31 +00:00
void *event_handler::create(int type, int size, int target)
{
void *p = &data[current_offset];
offsets[num_events] = current_offset;
types[num_events] = type;
sizes[num_events] = size;
2007-08-04 17:28:31 +00:00
targets[num_events] = target;
current_offset += size;
num_events++;
return p;
}
2007-05-22 15:03:32 +00:00
void event_handler::clear()
2007-05-22 15:03:32 +00:00
{
num_events = 0;
current_offset = 0;
}
2007-05-22 15:03:32 +00:00
void event_handler::snap(int snapping_client)
{
for(int i = 0; i < num_events; i++)
2007-05-22 15:03:32 +00:00
{
2007-08-04 17:28:31 +00:00
if (targets[i] == -1 || targets[i] == snapping_client)
{
void *d = snap_new_item(types[i], i, sizes[i]);
mem_copy(d, &data[offsets[i]], sizes[i]);
}
2007-05-22 15:03:32 +00:00
}
}
event_handler events;
//////////////////////////////////////////////////
// Entity
//////////////////////////////////////////////////
entity::entity(int objtype)
{
this->objtype = objtype;
pos = vec2(0,0);
flags = FLAG_ALIVE;
proximity_radius = 0;
2007-05-22 15:03:32 +00:00
current_id++;
id = current_id;
2007-07-14 13:09:42 +00:00
next_entity = 0;
prev_entity = 0;
prev_type_entity = 0;
next_type_entity = 0;
}
2007-05-22 15:03:32 +00:00
entity::~entity()
{
}
2007-05-22 15:03:32 +00:00
2007-07-13 13:40:04 +00:00
int entity::current_id = 1;
2007-05-22 15:03:32 +00:00
//////////////////////////////////////////////////
// game world
//////////////////////////////////////////////////
game_world::game_world()
2007-05-22 15:03:32 +00:00
{
paused = false;
reset_requested = false;
first_entity = 0x0;
for(int i = 0; i < NUM_ENT_TYPES; i++)
first_entity_types[i] = 0;
}
2007-07-14 13:09:42 +00:00
int game_world::find_entities(vec2 pos, float radius, entity **ents, int max)
{
int num = 0;
for(entity *ent = first_entity; ent; ent = ent->next_entity)
2007-05-22 15:03:32 +00:00
{
if(!(ent->flags&entity::FLAG_ALIVE))
continue;
if(distance(ent->pos, pos) < radius+ent->proximity_radius)
{
ents[num] = ent;
num++;
if(num == max)
break;
}
2007-05-22 15:03:32 +00:00
}
return num;
}
int game_world::find_entities(vec2 pos, float radius, entity **ents, int max, const int* types, int maxtypes)
{
int num = 0;
for(int t = 0; t < maxtypes; t++)
2007-05-22 15:03:32 +00:00
{
for(entity *ent = first_entity_types[types[t]]; ent; ent = ent->next_type_entity)
2007-05-22 15:03:32 +00:00
{
2007-07-14 13:09:42 +00:00
if(!(ent->flags&entity::FLAG_ALIVE))
continue;
2007-05-22 15:03:32 +00:00
if(distance(ent->pos, pos) < radius+ent->proximity_radius)
{
ents[num] = ent;
num++;
if(num == max)
break;
}
}
}
return num;
}
void game_world::insert_entity(entity *ent)
{
2007-07-25 07:24:57 +00:00
entity *cur = first_entity;
while(cur)
{
dbg_assert(cur != ent, "err");
cur = cur->next_entity;
}
// insert it
if(first_entity)
first_entity->prev_entity = ent;
ent->next_entity = first_entity;
ent->prev_entity = 0x0;
first_entity = ent;
// into typelist aswell
if(first_entity_types[ent->objtype])
first_entity_types[ent->objtype]->prev_type_entity = ent;
ent->next_type_entity = first_entity_types[ent->objtype];
ent->prev_type_entity = 0x0;
first_entity_types[ent->objtype] = ent;
}
void game_world::destroy_entity(entity *ent)
{
ent->set_flag(entity::FLAG_DESTROY);
}
void game_world::remove_entity(entity *ent)
{
// 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;
}
//
void game_world::snap(int snapping_client)
{
for(entity *ent = first_entity; ent; ent = ent->next_entity)
ent->snap(snapping_client);
}
void game_world::reset()
{
// reset all entities
for(entity *ent = first_entity; ent; ent = ent->next_entity)
ent->reset();
remove_entities();
2007-05-22 15:03:32 +00:00
for(entity *ent = first_entity; ent; ent = ent->next_entity)
ent->post_reset();
remove_entities();
2007-05-22 15:03:32 +00:00
reset_requested = false;
}
2007-07-14 13:09:42 +00:00
void game_world::remove_entities()
{
// destroy objects marked for destruction
entity *ent = first_entity;
while(ent)
2007-05-22 15:03:32 +00:00
{
entity *next = ent->next_entity;
if(ent->flags&entity::FLAG_DESTROY)
{
remove_entity(ent);
ent->destroy();
}
ent = next;
2007-05-22 15:03:32 +00:00
}
}
void game_world::tick()
{
if(reset_requested)
reset();
2007-05-22 15:03:32 +00:00
if(!paused)
2007-05-22 15:03:32 +00:00
{
// update all objects
for(entity *ent = first_entity; ent; ent = ent->next_entity)
ent->tick();
2007-07-13 13:40:04 +00:00
for(entity *ent = first_entity; ent; ent = ent->next_entity)
ent->tick_defered();
2007-05-22 15:03:32 +00:00
}
remove_entities();
}
2007-05-22 15:03:32 +00:00
game_world world;
2007-05-22 15:03:32 +00:00
//////////////////////////////////////////////////
// game object
//////////////////////////////////////////////////
gameobject::gameobject()
: entity(OBJTYPE_GAME)
2007-07-13 13:40:04 +00:00
{
gametype = GAMETYPE_DM;
game_over_tick = -1;
sudden_death = 0;
round_start_tick = server_tick();
}
2007-07-13 13:40:04 +00:00
void gameobject::endround()
2007-05-22 15:03:32 +00:00
{
world.paused = true;
game_over_tick = server_tick();
sudden_death = 0;
}
void gameobject::resetgame()
{
world.reset_requested = true;
}
void gameobject::startround()
{
resetgame();
2007-05-22 15:03:32 +00:00
round_start_tick = server_tick();
sudden_death = 0;
game_over_tick = -1;
world.paused = false;
}
void gameobject::post_reset()
{
for(int i = 0; i < MAX_CLIENTS; i++)
2007-05-22 15:03:32 +00:00
{
if(players[i].client_id != -1)
players[i].respawn();
2007-05-22 15:03:32 +00:00
}
}
2007-05-22 15:03:32 +00:00
2007-07-22 09:16:44 +00:00
void gameobject::tick_dm()
{
if(game_over_tick == -1)
2007-05-22 15:03:32 +00:00
{
// game is running
2007-07-13 13:40:04 +00:00
// gather some stats
int topscore = 0;
int topscore_count = 0;
for(int i = 0; i < MAX_CLIENTS; i++)
2007-05-22 15:03:32 +00:00
{
if(players[i].client_id != -1)
{
if(players[i].score > topscore)
{
topscore = players[i].score;
topscore_count = 1;
}
else if(players[i].score == topscore)
topscore_count++;
}
}
// check score win condition
if((config.scorelimit > 0 && topscore >= config.scorelimit) ||
(config.timelimit > 0 && (server_tick()-round_start_tick) >= config.timelimit*server_tickspeed()*60))
{
if(topscore_count == 1)
endround();
else
sudden_death = 1;
2007-05-22 15:03:32 +00:00
}
}
else
2007-05-22 15:03:32 +00:00
{
// game over.. wait for restart
if(server_tick() > game_over_tick+server_tickspeed()*10)
startround();
2007-05-22 15:03:32 +00:00
}
}
2007-05-22 15:03:32 +00:00
2007-07-22 09:16:44 +00:00
void gameobject::tick_tdm()
{
if(game_over_tick == -1)
{
// game is running
// gather some stats
int totalscore[2] = {0,0};
int topscore_count = 0;
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(players[i].client_id != -1)
totalscore[players[i].team] += players[i].score;
}
if (totalscore[0] >= config.scorelimit)
topscore_count++;
if (totalscore[1] >= config.scorelimit)
topscore_count++;
// check score win condition
if((config.scorelimit > 0 && (totalscore[0] >= config.scorelimit || totalscore[1] >= config.scorelimit)) ||
(config.timelimit > 0 && (server_tick()-round_start_tick) >= config.timelimit*server_tickspeed()*60))
{
if(topscore_count == 1)
endround();
else
sudden_death = 1;
}
}
else
{
// game over.. wait for restart
if(server_tick() > game_over_tick+server_tickspeed()*10)
startround();
}
}
void gameobject::tick()
{
switch(gametype)
{
case GAMETYPE_TDM:
{
tick_tdm();
break;
}
default:
{
tick_dm();
break;
}
}
}
void gameobject::snap(int snapping_client)
2007-05-22 15:03:32 +00:00
{
obj_game *game = (obj_game *)snap_new_item(OBJTYPE_GAME, id, sizeof(obj_game));
game->paused = world.paused;
game->game_over = game_over_tick==-1?0:1;
game->sudden_death = sudden_death;
2007-07-13 13:40:04 +00:00
game->score_limit = config.scorelimit;
game->time_limit = config.timelimit;
game->round_start_tick = round_start_tick;
2007-07-21 21:47:21 +00:00
game->gametype = gametype;
}
2007-07-22 09:16:44 +00:00
int gameobject::getteam(int notthisid)
{
int numplayers[2] = {0,0};
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(players[i].client_id != -1 && players[i].client_id != notthisid)
{
numplayers[players[i].team]++;
}
}
return numplayers[0] > numplayers[1] ? 1 : 0;
}
gameobject gameobj;
//////////////////////////////////////////////////
// projectile
//////////////////////////////////////////////////
projectile::projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner,
int damage, int flags, float force, int sound_impact, int weapon)
: entity(OBJTYPE_PROJECTILE)
{
this->type = type;
this->pos = pos;
this->vel = vel;
this->lifespan = span;
this->owner = owner;
this->powner = powner;
this->flags = flags;
this->force = force;
this->damage = damage;
this->sound_impact = sound_impact;
this->weapon = weapon;
2007-08-04 19:00:06 +00:00
this->bounce = 0;
world.insert_entity(this);
}
void projectile::reset()
{
world.destroy_entity(this);
}
void projectile::tick()
{
vec2 oldpos = pos;
2007-08-04 19:00:06 +00:00
int collide = 0;
if(bounce)
{
int numbounces;
vel.y += 0.25f;
move_point(&pos, &vel, 0.25f, &numbounces);
2007-08-04 19:00:06 +00:00
bounce -= numbounces;
}
else
{
vel.y += 0.25f;
pos += vel;
collide = col_check_point((int)pos.x, (int)pos.y);
}
lifespan--;
2007-07-13 13:40:04 +00:00
// check player intersection as well
vec2 new_pos;
entity *targetplayer = (entity*)intersect_player(oldpos, pos, new_pos, powner);
2007-08-04 19:00:06 +00:00
if(targetplayer || lifespan < 0 || collide || bounce < 0)
2007-05-22 15:03:32 +00:00
{
2007-07-26 14:08:56 +00:00
if (lifespan >= 0 || weapon == WEAPON_ROCKET)
create_sound(pos, sound_impact);
if (flags & PROJECTILE_FLAGS_EXPLODE)
create_explosion(oldpos, owner, weapon, false);
else if (targetplayer)
2007-08-04 17:28:31 +00:00
{
targetplayer->take_damage(normalize(vel) * max(0.001f, force), damage, owner, weapon);
2007-08-04 17:28:31 +00:00
}
world.destroy_entity(this);
}
}
2007-05-22 15:03:32 +00:00
void projectile::snap(int snapping_client)
{
obj_projectile *proj = (obj_projectile *)snap_new_item(OBJTYPE_PROJECTILE, id, sizeof(obj_projectile));
proj->x = (int)pos.x;
proj->y = (int)pos.y;
proj->vx = (int)vel.x; // TODO: should be an angle
proj->vy = (int)vel.y;
proj->type = type;
}
2007-05-22 15:03:32 +00:00
//////////////////////////////////////////////////
// projectile_backpackrocket
//////////////////////////////////////////////////
projectile_backpackrocket::projectile_backpackrocket(baselib::vec2 pos, baselib::vec2 target, int owner, entity* powner)
: projectile(WEAPON_PROJECTILETYPE_ROCKET, owner, pos, vec2(0,0), 100, powner, 0, 0, 0, -1, WEAPON_ROCKET_BACKPACK)
{
stage = 0;
start_tick = server_tick();
vel = vec2(0,-10.0f);
this->target = target;
start = pos;
midpoint = pos;
midpoint.y = target.y;
direction = normalize(target-midpoint);
deply_ticks = (int)( distance(start, midpoint)/(float)server_tickspeed() * 5.0f );
dbg_msg("rocket_bp", "%f %d", distance(start, midpoint), deply_ticks);
}
void projectile_backpackrocket::tick()
{
lifespan--;
if(!lifespan)
world.destroy_entity(this);
vec2 oldpos = pos;
if(stage == 0)
{
float time = (server_tick()-start_tick)/(float)(deply_ticks);
if(midpoint.y > start.y)
pos.y = mix(start.y, midpoint.y, 1-sinf((1-time)*pi/2));
else
pos.y = mix(start.y, midpoint.y, sinf(time*pi/2));
float a = (server_tick()-start_tick)/(float)server_tickspeed()*pi*7.5f;
vel.x = sinf(a)*30.0f;
vel.y = cosf(a)*30.0f;
if(server_tick() > start_tick+deply_ticks)
{
pos = midpoint;
direction = normalize(target-pos);
vel = vec2(0,0);
stage = 1;
}
}
else if(stage == 1)
{
vel += direction*1.5f;
vel.x = clamp(vel.x, -20.0f, 20.0f);
pos += vel;
}
// check player intersection as well
vec2 new_pos;
entity *targetplayer = (entity*)intersect_player(oldpos, pos, new_pos, powner);
if(targetplayer || lifespan < 0 || col_check_point((int)pos.x, (int)pos.y))
{
if (lifespan >= 0)
create_sound(pos, sound_impact);
create_explosion(oldpos, owner, weapon, false);
world.destroy_entity(this);
}
}
//////////////////////////////////////////////////
// player
//////////////////////////////////////////////////
// TODO: move to separate file
player::player()
: entity(OBJTYPE_PLAYER)
{
init();
}
void player::init()
{
proximity_radius = phys_size;
name[0] = 'n';
name[1] = 'o';
name[2] = 'o';
name[3] = 'b';
name[4] = 0;
client_id = -1;
2007-07-22 13:46:45 +00:00
team = 0;
2007-07-21 19:03:50 +00:00
extrapowerflags = 0;
ninjaactivationtick = 0;
reset();
}
void player::reset()
{
release_hooked();
release_hooks();
2007-07-14 13:09:42 +00:00
pos = vec2(0.0f, 0.0f);
vel = vec2(0.0f, 0.0f);
direction = vec2(0.0f, 1.0f);
score = 0;
dead = true;
2007-08-02 20:17:18 +00:00
spawning = false;
die_tick = 0;
damage_taken = 0;
2007-07-26 11:33:49 +00:00
state = STATE_UNKNOWN;
}
2007-05-22 15:03:32 +00:00
void player::destroy() { }
2007-07-13 13:40:04 +00:00
void player::respawn()
{
2007-08-02 20:17:18 +00:00
spawning = true;
}
void player::try_respawn()
{
// get spawn point
int start, num;
map_get_type(1, &start, &num);
vec2 spawnpos = vec2(100.0f, -60.0f);
if(num)
{
mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + (rand()%num), NULL, NULL);
spawnpos = vec2((float)sp->x, (float)sp->y);
}
// check if the position is occupado
entity *ents[2] = {0};
int types[] = {OBJTYPE_PLAYER};
int num_ents = world.find_entities(spawnpos, 64, ents, 2, types, 1);
for(int i = 0; i < num_ents; i++)
{
if(ents[i] != this)
return;
}
spawning = false;
pos = spawnpos;
defered_pos = pos;
2007-07-21 19:03:50 +00:00
health = data->playerinfo[gameobj.gametype].maxhealth;
armor = 0;
jumped = 0;
dead = false;
set_flag(entity::FLAG_ALIVE);
2007-07-26 11:33:49 +00:00
state = STATE_PLAYING;
2007-05-22 15:03:32 +00:00
mem_zero(&input, sizeof(input));
vel = vec2(0.0f, 0.0f);
// init weapons
mem_zero(&weapons, sizeof(weapons));
weapons[WEAPON_HAMMER].got = true;
weapons[WEAPON_HAMMER].ammo = -1;
weapons[WEAPON_GUN].got = true;
2007-07-21 19:03:50 +00:00
weapons[WEAPON_GUN].ammo = data->weapons[active_weapon].maxammo;
2007-08-04 01:53:26 +00:00
active_weapon = WEAPON_GUN;
reload_timer = 0;
2007-07-21 19:03:50 +00:00
// Create sound and spawn effects
create_sound(pos, SOUND_PLAYER_SPAWN);
2007-07-21 19:03:50 +00:00
create_spawn(pos);
}
2007-05-22 15:03:32 +00:00
bool player::is_grounded()
{
if(col_check_point((int)(pos.x+phys_size/2), (int)(pos.y+phys_size/2+5)))
return true;
if(col_check_point((int)(pos.x-phys_size/2), (int)(pos.y+phys_size/2+5)))
return true;
return false;
}
2007-05-22 15:03:32 +00:00
// releases the hooked player
void player::release_hooked()
{
hook_state = HOOK_RETRACTED;
hooked_player = 0x0;
}
// release all hooks to this player
void player::release_hooks()
{
// TODO: loop thru players only
for(entity *ent = world.first_entity; ent; ent = ent->next_entity)
2007-05-22 15:03:32 +00:00
{
if(ent && ent->objtype == OBJTYPE_PLAYER)
2007-05-22 15:03:32 +00:00
{
player *p = (player*)ent;
if(p->hooked_player == this)
p->release_hooked();
2007-05-22 15:03:32 +00:00
}
}
}
2007-07-21 19:03:50 +00:00
int player::handle_ninja()
{
if ((server_tick() - ninjaactivationtick) > (data->weapons[WEAPON_NINJA].duration * server_tickspeed() / 1000))
{
// time's up, return
active_weapon = WEAPON_GUN;
return 0;
}
// Check if it should activate
if ((input.fire && !(previnput.fire)) && (server_tick() > currentcooldown))
{
// ok then, activate ninja
attack_tick = server_tick();
activationdir = direction;
currentmovetime = data->weapons[WEAPON_NINJA].movetime * server_tickspeed() / 1000;
currentcooldown = data->weapons[WEAPON_NINJA].firedelay * server_tickspeed() / 1000 + server_tick();
// reset hit objects
numobjectshit = 0;
create_sound(pos, SOUND_NINJA_FIRE);
// release all hooks when ninja is activated
release_hooked();
release_hooks();
2007-07-21 19:03:50 +00:00
}
currentmovetime--;
if (currentmovetime == 0)
{
// reset player velocity
vel *= 0.2f;
//return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON;
}
if (currentmovetime > 0)
{
// Set player velocity
vel = activationdir * data->weapons[WEAPON_NINJA].velocity;
vec2 oldpos = pos;
move_box(&defered_pos, &vel, vec2(phys_size, phys_size), 0.0f);
// reset velocity so the client doesn't predict stuff
vel = vec2(0.0f,0.0f);
if ((currentmovetime % 2) == 0)
{
create_smoke(pos);
}
// check if we hit anything along the way
{
int type = OBJTYPE_PLAYER;
entity *ents[64];
vec2 dir = pos - oldpos;
2007-07-22 13:46:45 +00:00
float radius = phys_size * 2.0f; //length(dir * 0.5f);
2007-07-21 19:03:50 +00:00
vec2 center = oldpos + dir * 0.5f;
int num = world.find_entities(center, radius, ents, 64, &type, 1);
for (int i = 0; i < num; i++)
{
// Check if entity is a player
if (ents[i] == this)
continue;
// make sure we haven't hit this object before
bool balreadyhit = false;
for (int j = 0; j < numobjectshit; j++)
{
if (hitobjects[j] == ents[i])
balreadyhit = true;
}
if (balreadyhit)
continue;
// check so we are sufficiently close
if (distance(ents[i]->pos, pos) > (phys_size * 2.0f))
continue;
// hit a player, give him damage and stuffs...
create_sound(ents[i]->pos, SOUND_NINJA_HIT);
// set his velocity to fast upward (for now)
hitobjects[numobjectshit++] = ents[i];
2007-08-05 08:59:38 +00:00
ents[i]->take_damage(vec2(0,10.0f), data->weapons[WEAPON_NINJA].meleedamage, client_id,WEAPON_NINJA);
2007-07-21 19:03:50 +00:00
}
}
return MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION | MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY;
}
return 0;
}
int player::handle_weapons()
{
// check reload timer
if(reload_timer)
2007-05-22 15:03:32 +00:00
{
reload_timer--;
2007-07-21 19:03:50 +00:00
return 0;
}
if (active_weapon == WEAPON_NINJA)
{
// don't update other weapons while ninja is active
return handle_ninja();
}
2007-05-22 15:03:32 +00:00
// switch weapon if wanted
2007-07-21 19:03:50 +00:00
if(input.activeweapon >= 0 && input.activeweapon < NUM_WEAPONS && weapons[input.activeweapon].got &&
data->weapons[active_weapon].duration <= 0)
{
if (active_weapon != input.activeweapon)
create_sound(pos, SOUND_WEAPON_SWITCH);
active_weapon = input.activeweapon;
}
2007-05-22 15:03:32 +00:00
2007-07-21 19:03:50 +00:00
if(!previnput.fire && input.fire)
{
if(reload_timer == 0)
2007-07-13 13:40:04 +00:00
{
// fire!
if(weapons[active_weapon].ammo)
2007-05-22 15:03:32 +00:00
{
switch(active_weapon)
2007-05-22 15:03:32 +00:00
{
case WEAPON_HAMMER:
2007-07-21 19:03:50 +00:00
// reset objects hit
numobjectshit = 0;
2007-07-21 17:35:28 +00:00
create_sound(pos, SOUND_HAMMER_FIRE);
break;
2007-07-13 13:40:04 +00:00
case WEAPON_GUN:
new projectile(projectile::WEAPON_PROJECTILETYPE_GUN,
client_id,
pos+vec2(0,0),
direction*30.0f,
100,
this,
1, 0, 0, -1, WEAPON_GUN);
2007-07-21 17:35:28 +00:00
create_sound(pos, SOUND_GUN_FIRE);
break;
case WEAPON_ROCKET:
2007-08-04 19:00:06 +00:00
{
2007-08-05 08:59:38 +00:00
new projectile(projectile::WEAPON_PROJECTILETYPE_ROCKET,
client_id,
pos+vec2(0,0),
direction*15.0f,
100,
this,
2007-07-21 17:35:28 +00:00
1, projectile::PROJECTILE_FLAGS_EXPLODE, 0, SOUND_ROCKET_EXPLODE, WEAPON_ROCKET);
create_sound(pos, SOUND_ROCKET_FIRE);
break;
2007-08-04 19:00:06 +00:00
}
case WEAPON_SHOTGUN:
2007-08-04 01:57:01 +00:00
{
int shotspread = 2;
2007-08-04 01:53:26 +00:00
for(int i = -shotspread; i <= shotspread; i++)
{
float a = get_angle(direction);
2007-08-04 19:00:06 +00:00
a += i*0.08f;
new projectile(projectile::WEAPON_PROJECTILETYPE_SHOTGUN,
2007-07-13 13:40:04 +00:00
client_id,
pos+vec2(0,0),
vec2(cosf(a), sinf(a))*25.0f,
//vec2(cosf(a), sinf(a))*20.0f,
2007-08-04 19:00:06 +00:00
(int)(server_tickspeed()*0.4f),
2007-07-13 13:40:04 +00:00
this,
2007-08-04 19:00:06 +00:00
1, 0, 0, -1, WEAPON_SHOTGUN);
}
2007-07-21 17:35:28 +00:00
create_sound(pos, SOUND_SHOTGUN_FIRE);
break;
2007-08-04 01:57:01 +00:00
}
case WEAPON_ROCKET_BACKPACK:
new projectile_backpackrocket(
pos+vec2(0,0),
pos+vec2(input.target_x,input.target_y),
client_id,
this);
break;
2007-05-22 15:03:32 +00:00
}
weapons[active_weapon].ammo--;
2007-07-21 19:03:50 +00:00
attack_tick = server_tick();
reload_timer = data->weapons[active_weapon].firedelay * server_tickspeed() / 1000;
}
else
{
create_sound(pos, SOUND_WEAPON_NOAMMO);
2007-05-22 15:03:32 +00:00
}
}
}
2007-07-21 19:03:50 +00:00
// Update weapons
if (active_weapon == WEAPON_HAMMER && reload_timer > 0)
{
// Handle collisions
// only one that needs update (for now)
// do selection for the weapon and bash anything in it
// check if we hit anything along the way
int type = OBJTYPE_PLAYER;
entity *ents[64];
vec2 lookdir(direction.x > 0.0f ? 1.0f : -1.0f, 0.0f);
vec2 dir = lookdir * data->weapons[active_weapon].meleereach;
float radius = length(dir * 0.5f);
vec2 center = pos + dir * 0.5f;
int num = world.find_entities(center, radius, ents, 64, &type, 1);
for (int i = 0; i < num; i++)
{
// Check if entity is a player
if (ents[i] == this)
continue;
// make sure we haven't hit this object before
bool balreadyhit = false;
for (int j = 0; j < numobjectshit; j++)
{
if (hitobjects[j] == ents[i])
balreadyhit = true;
}
if (balreadyhit)
continue;
// check so we are sufficiently close
if (distance(ents[i]->pos, pos) > (phys_size * 2.0f))
continue;
// hit a player, give him damage and stuffs...
// create sound for bash
//create_sound(ents[i]->pos, sound_impact);
2007-08-04 19:00:06 +00:00
vec2 fdir = normalize(ents[i]->pos- pos);
2007-07-21 19:03:50 +00:00
// set his velocity to fast upward (for now)
create_smoke(ents[i]->pos);
2007-07-22 14:16:57 +00:00
create_sound(pos, SOUND_HAMMER_HIT);
2007-07-21 19:03:50 +00:00
hitobjects[numobjectshit++] = ents[i];
2007-08-04 19:00:06 +00:00
ents[i]->take_damage(vec2(0,-1.0f), data->weapons[active_weapon].meleedamage, client_id, active_weapon);
2007-07-21 19:03:50 +00:00
player* target = (player*)ents[i];
vec2 dir;
if (length(target->pos - pos) > 0.0f)
dir = normalize(target->pos - pos);
else
dir = vec2(0,-1);
2007-08-04 19:00:06 +00:00
target->vel += dir * 25.0f + vec2(0,-5.0f);
2007-07-21 19:03:50 +00:00
}
}
if (data->weapons[active_weapon].ammoregentime)
{
// If equipped and not active, regen ammo?
if (reload_timer <= 0)
{
if (weapons[active_weapon].ammoregenstart < 0)
weapons[active_weapon].ammoregenstart = server_tick();
if ((server_tick() - weapons[active_weapon].ammoregenstart) >= data->weapons[active_weapon].ammoregentime * server_tickspeed() / 1000)
{
// Add some ammo
weapons[active_weapon].ammo = min(weapons[active_weapon].ammo + 1, data->weapons[active_weapon].maxammo);
weapons[active_weapon].ammoregenstart = -1;
}
}
else
{
weapons[active_weapon].ammoregenstart = -1;
}
}
return 0;
}
void player::tick()
{
// 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;
}
}
2007-08-02 20:17:18 +00:00
if(spawning)
try_respawn();
// TODO: rework the input to be more robust
// TODO: remove this tick count, it feels weird
if(dead)
{
2007-07-22 12:27:49 +00:00
if(server_tick()-die_tick >= server_tickspeed()*5) // auto respawn after 3 sec
respawn();
if(input.fire && server_tick()-die_tick >= server_tickspeed()/2) // auto respawn after 0.5 sec
respawn();
return;
}
// fetch some info
bool grounded = is_grounded();
direction = normalize(vec2(input.target_x, input.target_y));
2007-05-22 15:03:32 +00:00
float max_speed = grounded ? ground_control_speed : air_control_speed;
float accel = grounded ? ground_control_accel : air_control_accel;
float friction = grounded ? ground_friction : air_friction;
// handle movement
if(input.left)
vel.x = saturated_add(-max_speed, max_speed, vel.x, -accel);
if(input.right)
vel.x = saturated_add(-max_speed, max_speed, vel.x, accel);
if(!input.left && !input.right)
vel.x *= friction;
// handle jumping
if(input.jump)
2007-05-22 15:03:32 +00:00
{
if(!jumped && grounded)
2007-07-14 13:09:42 +00:00
{
create_sound(pos, SOUND_PLAYER_JUMP);
vel.y = -ground_jump_speed;
jumped++;
2007-07-14 13:09:42 +00:00
}
}
else
jumped = 0;
2007-05-22 15:03:32 +00:00
// do hook
if(input.hook)
{
if(hook_state == HOOK_IDLE)
2007-05-22 15:03:32 +00:00
{
hook_state = HOOK_FLYING;
hook_pos = pos;
hook_dir = direction;
hook_tick = -1;
2007-05-22 15:03:32 +00:00
}
else if(hook_state == HOOK_FLYING)
2007-05-22 15:03:32 +00:00
{
vec2 new_pos = hook_pos+hook_dir*hook_fire_speed;
2007-07-13 13:40:04 +00:00
// Check against other players first
for(entity *ent = world.first_entity; ent; ent = ent->next_entity)
{
if(ent && ent->objtype == OBJTYPE_PLAYER)
2007-05-22 15:03:32 +00:00
{
player *p = (player*)ent;
2007-07-29 22:20:00 +00:00
if(p != this && !p->dead && distance(p->pos, new_pos) < p->phys_size)
2007-05-22 15:03:32 +00:00
{
2007-07-13 13:40:04 +00:00
hook_state = HOOK_GRABBED;
hooked_player = p;
break;
2007-05-22 15:03:32 +00:00
}
}
2007-07-13 13:40:04 +00:00
}
if(hook_state == HOOK_FLYING)
2007-07-13 13:40:04 +00:00
{
// check against ground
if(col_intersect_line(hook_pos, new_pos, &new_pos))
2007-05-22 15:03:32 +00:00
{
hook_state = HOOK_GRABBED;
hook_pos = new_pos;
2007-05-22 15:03:32 +00:00
}
else if(distance(pos, new_pos) > hook_length)
2007-05-22 15:03:32 +00:00
{
hook_state = HOOK_RETRACTED;
2007-05-22 15:03:32 +00:00
}
else
hook_pos = new_pos;
2007-07-13 13:40:04 +00:00
}
if(hook_state == HOOK_GRABBED)
{
create_sound(pos, SOUND_HOOK_ATTACH);
hook_tick = server_tick();
}
2007-05-22 15:03:32 +00:00
}
2007-07-13 13:40:04 +00:00
}
else
2007-05-22 15:03:32 +00:00
{
2007-07-13 13:40:04 +00:00
release_hooked();
hook_state = HOOK_IDLE;
hook_pos = pos;
2007-05-22 15:03:32 +00:00
}
if(hook_state == HOOK_GRABBED)
2007-05-22 15:03:32 +00:00
{
2007-07-22 09:15:34 +00:00
if(hooked_player)
{
2007-07-22 09:15:34 +00:00
hook_pos = hooked_player->pos;
// keep players hooked for a max of 1.5sec
if(server_tick() > hook_tick+(server_tickspeed()*3)/2)
release_hooked();
}
2007-07-21 19:03:50 +00:00
/*if(hooked_player)
hook_pos = hooked_player->pos;
2007-07-14 22:35:46 +00:00
float d = distance(pos, hook_pos);
vec2 dir = normalize(pos - hook_pos);
if(d > 10.0f) // TODO: fix tweakable variable
2007-05-22 15:03:32 +00:00
{
float accel = hook_drag_accel * (d/hook_length);
vel.x = saturated_add(-hook_drag_speed, hook_drag_speed, vel.x, -accel*dir.x*0.75f);
vel.y = saturated_add(-hook_drag_speed, hook_drag_speed, vel.y, -accel*dir.y);
2007-07-21 19:03:50 +00:00
}*/
2007-07-22 09:15:34 +00:00
// Old version feels much better (to me atleast)
if(distance(hook_pos, pos) > 46.0f)
{
vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel;
// the hook as more power to drag you up then down.
// this makes it easier to get on top of an platform
if(hookvel.y > 0)
hookvel.y *= 0.3f;
// the hook will boost it's power if the player wants to move
// in that direction. otherwise it will dampen everything abit
if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right))
hookvel.x *= 0.95f;
else
hookvel.x *= 0.75f;
vec2 new_vel = vel+hookvel;
// check if we are under the legal limit for the hook
if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel))
vel = new_vel; // no problem. apply
}
}
2007-05-22 15:03:32 +00:00
// fix influence of other players, collision + hook
// TODO: loop thru players only
for(entity *ent = world.first_entity; ent; ent = ent->next_entity)
{
if(ent && ent->objtype == OBJTYPE_PLAYER)
2007-05-22 15:03:32 +00:00
{
player *p = (player*)ent;
if(p == this || !(p->flags&FLAG_ALIVE))
continue; // make sure that we don't nudge our self
// handle player <-> player collision
float d = distance(pos, p->pos);
vec2 dir = normalize(pos - p->pos);
if(d < phys_size*1.25f)
2007-05-22 15:03:32 +00:00
{
float a = phys_size*1.25f - d;
vel = vel + dir*a;
}
// handle hook influence
if(p->hooked_player == this)
{
if(d > phys_size*1.50f) // TODO: fix tweakable variable
2007-05-22 15:03:32 +00:00
{
float accel = hook_drag_accel * (d/hook_length);
vel.x = saturated_add(-hook_drag_speed, hook_drag_speed, vel.x, -accel*dir.x);
vel.y = saturated_add(-hook_drag_speed, hook_drag_speed, vel.y, -accel*dir.y);
2007-05-22 15:03:32 +00:00
}
}
}
}
// handle weapons
2007-07-21 19:03:50 +00:00
int retflags = handle_weapons();
if (!(retflags & (MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION)))
{
// add gravity
if (!(retflags & MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY))
vel.y += gravity;
2007-07-21 19:03:50 +00:00
// do the move
defered_pos = pos;
move_box(&defered_pos, &vel, vec2(phys_size, phys_size), 0);
}
2007-07-26 11:33:49 +00:00
state = input.state;
2007-07-21 19:03:50 +00:00
// Previnput
previnput = input;
return;
}
2007-05-22 15:03:32 +00:00
void player::tick_defered()
{
// apply the new position
pos = defered_pos;
}
2007-05-22 15:03:32 +00:00
void player::die(int killer, int weapon)
{
// send the kill message
msg_pack_start(MSG_KILLMSG, MSGFLAG_VITAL);
msg_pack_int(killer);
msg_pack_int(client_id);
msg_pack_int(weapon);
msg_pack_end();
server_send_msg(-1);
// a nice sound
create_sound(pos, SOUND_PLAYER_DIE);
// release all hooks
release_hooked();
release_hooks();
// set dead state
dead = true;
die_tick = server_tick();
clear_flag(entity::FLAG_ALIVE);
2007-07-21 21:17:38 +00:00
create_death(pos);
}
2007-05-22 15:03:32 +00:00
bool player::take_damage(vec2 force, int dmg, int from, int weapon)
{
vel += force;
2007-08-04 19:00:06 +00:00
// player only inflicts half damage on self
if(from == client_id)
dmg = max(1, dmg/2);
2007-07-22 12:32:57 +00:00
if (gameobj.gametype == GAMETYPE_TDM && from >= 0 && players[from].team == team)
return false;
damage_taken++;
// create healthmod indicator
if(server_tick() < damage_taken_tick+25)
{
// make sure that the damage indicators doesn't group together
create_damageind(pos, damage_taken*0.25f, dmg);
}
else
{
damage_taken = 0;
create_damageind(pos, 0, dmg);
}
if(armor)
2007-05-22 15:03:32 +00:00
{
armor -= 1;
dmg--;
}
if(dmg > armor)
{
dmg -= armor;
armor = 0;
health -= dmg;
}
else
armor -= dmg;
damage_taken_tick = server_tick();
2007-08-05 08:59:38 +00:00
// do damage hit sound
if(from >= 0)
create_targetted_sound(pos, SOUND_HIT, from);
// check for death
if(health <= 0)
{
// apply score
if(from != -1)
2007-05-22 15:03:32 +00:00
{
if(from == client_id)
score--;
else
{
player *p = get_player(from);
p->score++;
}
2007-05-22 15:03:32 +00:00
}
die(from, weapon);
return false;
}
if (dmg > 2)
create_sound(pos, SOUND_PLAYER_PAIN_LONG);
else
create_sound(pos, SOUND_PLAYER_PAIN_SHORT);
2007-08-05 08:59:38 +00:00
// spawn blood?
return true;
}
void player::snap(int snaping_client)
{
obj_player *player = (obj_player *)snap_new_item(OBJTYPE_PLAYER, client_id, sizeof(obj_player));
player->x = (int)pos.x;
player->y = (int)pos.y;
player->vx = (int)vel.x;
player->vy = (int)vel.y;
player->emote = EMOTE_NORMAL;
player->latency = latency_avg;
player->latency_flux = latency_max-latency_min;
2007-07-22 09:15:34 +00:00
player->ammocount = weapons[active_weapon].ammo;
player->health = 0;
player->armor = 0;
player->local = 0;
player->clientid = client_id;
player->weapon = active_weapon;
player->attacktick = attack_tick;
if(client_id == snaping_client)
{
player->local = 1;
player->health = health;
player->armor = armor;
}
if(dead)
player->health = -1;
if(length(vel) > 15.0f)
player->emote = EMOTE_HAPPY;
if(damage_taken_tick+50 > server_tick())
player->emote = EMOTE_PAIN;
if(player->emote == EMOTE_NORMAL)
{
if((server_tick()%(50*5)) < 10)
player->emote = EMOTE_BLINK;
2007-05-22 15:03:32 +00:00
}
player->hook_active = hook_state>0?1:0;
player->hook_x = (int)hook_pos.x;
player->hook_y = (int)hook_pos.y;
float a = atan((float)input.target_y/(float)input.target_x);
if(input.target_x < 0)
a = a+pi;
player->angle = (int)(a*256.0f);
player->score = score;
2007-07-21 21:47:21 +00:00
player->team = team;
2007-07-26 11:33:49 +00:00
player->state = state;
}
2007-05-22 15:03:32 +00:00
player players[MAX_CLIENTS];
2007-05-22 15:03:32 +00:00
//////////////////////////////////////////////////
// powerup
//////////////////////////////////////////////////
2007-07-13 13:40:04 +00:00
powerup::powerup(int _type, int _subtype)
: entity(OBJTYPE_POWERUP)
2007-05-22 15:03:32 +00:00
{
type = _type;
subtype = _subtype;
proximity_radius = phys_size;
reset();
2007-05-22 15:03:32 +00:00
2007-07-13 13:40:04 +00:00
// TODO: should this be done here?
world.insert_entity(this);
2007-05-22 15:03:32 +00:00
}
void powerup::reset()
{
2007-07-23 17:52:04 +00:00
if (data->powerupinfo[type].startspawntime > 0)
spawntick = server_tick() + server_tickspeed() * data->powerupinfo[type].startspawntime;
else
spawntick = -1;
}
2007-05-22 15:03:32 +00:00
void powerup::tick()
{
// wait for respawn
if(spawntick > 0)
{
if(server_tick() > spawntick)
{
// respawn
2007-05-22 15:03:32 +00:00
spawntick = -1;
if(type == POWERUP_WEAPON)
create_sound(pos, SOUND_WEAPON_SPAWN, 0);
}
2007-05-22 15:03:32 +00:00
else
return;
}
// Check if a player intersected us
vec2 meh;
player* pplayer = intersect_player(pos, pos + vec2(0,16), meh, 0);
if (pplayer)
{
// player picked us up, is someone was hooking us, let them go
int respawntime = -1;
switch (type)
{
2007-07-23 17:52:04 +00:00
case POWERUP_HEALTH:
2007-07-21 19:03:50 +00:00
if(pplayer->health < data->playerinfo[gameobj.gametype].maxhealth)
2007-05-22 15:03:32 +00:00
{
create_sound(pos, SOUND_PICKUP_HEALTH, 0);
2007-07-23 17:52:04 +00:00
pplayer->health = min((int)data->playerinfo[gameobj.gametype].maxhealth, pplayer->health + data->powerupinfo[type].amount);
respawntime = data->powerupinfo[type].respawntime;
2007-05-22 15:03:32 +00:00
}
2007-07-13 13:40:04 +00:00
break;
2007-07-23 17:52:04 +00:00
case POWERUP_ARMOR:
2007-07-21 19:03:50 +00:00
if(pplayer->armor < data->playerinfo[gameobj.gametype].maxarmor)
2007-05-22 15:03:32 +00:00
{
create_sound(pos, SOUND_PICKUP_ARMOR, 0);
2007-07-23 17:52:04 +00:00
pplayer->armor = min((int)data->playerinfo[gameobj.gametype].maxarmor, pplayer->armor + data->powerupinfo[type].amount);
respawntime = data->powerupinfo[type].respawntime;
2007-05-22 15:03:32 +00:00
}
2007-07-13 13:40:04 +00:00
break;
2007-07-23 17:52:04 +00:00
case POWERUP_WEAPON:
2007-07-13 13:40:04 +00:00
if(subtype >= 0 && subtype < NUM_WEAPONS)
2007-05-22 15:03:32 +00:00
{
2007-07-13 13:40:04 +00:00
if(pplayer->weapons[subtype].ammo < 10 || !pplayer->weapons[subtype].got)
2007-05-22 15:03:32 +00:00
{
2007-07-13 13:40:04 +00:00
pplayer->weapons[subtype].got = true;
2007-07-23 17:52:04 +00:00
pplayer->weapons[subtype].ammo = min(10, pplayer->weapons[subtype].ammo + data->powerupinfo[type].amount);
respawntime = data->powerupinfo[type].respawntime;
2007-08-03 06:35:35 +00:00
// TODO: data compiler should take care of stuff like this
if(subtype == WEAPON_ROCKET)
create_sound(pos, SOUND_PICKUP_ROCKET);
else if(subtype == WEAPON_SHOTGUN)
create_sound(pos, SOUND_PICKUP_SHOTGUN);
2007-05-22 15:03:32 +00:00
}
}
2007-07-13 13:40:04 +00:00
break;
2007-07-23 17:52:04 +00:00
case POWERUP_NINJA:
2007-07-21 19:03:50 +00:00
{
// activate ninja on target player
pplayer->ninjaactivationtick = server_tick();
pplayer->weapons[WEAPON_NINJA].got = true;
pplayer->active_weapon = WEAPON_NINJA;
2007-07-23 17:52:04 +00:00
respawntime = data->powerupinfo[type].respawntime;
2007-08-03 06:35:35 +00:00
create_sound(pos, SOUND_PICKUP_NINJA);
2007-07-21 19:03:50 +00:00
break;
}
2007-05-22 15:03:32 +00:00
default:
break;
};
if(respawntime >= 0)
spawntick = server_tick() + server_tickspeed() * respawntime;
}
}
void powerup::snap(int snapping_client)
{
if(spawntick != -1)
return;
2007-07-13 13:40:04 +00:00
obj_powerup *up = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup));
up->x = (int)pos.x;
up->y = (int)pos.y;
up->type = type; // TODO: two diffrent types? what gives?
up->subtype = subtype;
2007-05-22 15:03:32 +00:00
}
// POWERUP END ///////////////////////
2007-07-26 09:21:47 +00:00
//////////////////////////////////////////////////
// FLAG
//////////////////////////////////////////////////
flag::flag(int _team)
: entity(OBJTYPE_FLAG)
{
team = _team;
proximity_radius = phys_size;
carrying_player = 0x0;
reset();
// TODO: should this be done here?
world.insert_entity(this);
}
void flag::reset()
{
spawntick = -1;
}
void flag::tick()
{
// wait for respawn
if(spawntick > 0)
{
if(server_tick() > spawntick)
spawntick = -1;
else
return;
}
// Check if a player intersected us
vec2 meh;
player* pplayer = intersect_player(pos, pos + vel, meh, 0);
if (pplayer)
{
if (!carrying_player)
carrying_player = pplayer;
// TODO: something..?
}
if (carrying_player)
{
if (carrying_player->dead)
carrying_player = 0x0;
else
{
vel = carrying_player->pos - pos;
pos = carrying_player->pos;
}
}
if (!carrying_player)
{
vel.y += 0.25f;
vec2 new_pos = pos + vel;
col_intersect_line(pos, new_pos, &new_pos);
pos = new_pos;
if (is_grounded())
vel.x = vel.y = 0;
}
}
bool flag::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;
}
void flag::snap(int snapping_client)
{
if(spawntick != -1)
return;
obj_flag *flag = (obj_flag *)snap_new_item(OBJTYPE_FLAG, id, sizeof(obj_flag));
flag->x = (int)pos.x;
flag->y = (int)pos.y;
flag->team = team;
}
// FLAG END ///////////////////////
2007-05-22 15:03:32 +00:00
player *get_player(int index)
{
return &players[index];
}
void create_damageind(vec2 p, float angle, int amount)
2007-05-22 15:03:32 +00:00
{
float a = 3 * 3.14159f / 2 + angle;
2007-07-21 12:57:36 +00:00
//float a = get_angle(dir);
2007-07-14 13:09:42 +00:00
float s = a-pi/3;
float e = a+pi/3;
for(int i = 0; i < amount; i++)
{
float f = mix(s, e, float(i+1)/float(amount+2));
ev_damageind *ev = (ev_damageind *)events.create(EVENT_DAMAGEINDICATION, sizeof(ev_damageind));
ev->x = (int)p.x;
ev->y = (int)p.y;
ev->angle = (int)(f*256.0f);
}
2007-05-22 15:03:32 +00:00
}
void create_explosion(vec2 p, int owner, int weapon, bool bnodamage)
2007-05-22 15:03:32 +00:00
{
// create the event
ev_explosion *ev = (ev_explosion *)events.create(EVENT_EXPLOSION, sizeof(ev_explosion));
ev->x = (int)p.x;
ev->y = (int)p.y;
if (!bnodamage)
{
// deal damage
entity *ents[64];
const float radius = 128.0f;
const float innerradius = 42.0f;
2007-05-22 15:03:32 +00:00
int num = world.find_entities(p, radius, ents, 64);
for(int i = 0; i < num; i++)
{
vec2 diff = ents[i]->pos - p;
vec2 forcedir(0,1);
float l = length(diff);
if(l)
forcedir = normalize(diff);
l = 1-clamp((l-innerradius)/(radius-innerradius), 0.0f, 1.0f);
float dmg = 6 * l;
2007-05-22 15:03:32 +00:00
if((int)dmg)
2007-08-04 19:00:06 +00:00
ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner, weapon);
2007-05-22 15:03:32 +00:00
}
}
}
void create_smoke(vec2 p)
{
// create the event
ev_explosion *ev = (ev_explosion *)events.create(EVENT_SMOKE, sizeof(ev_explosion));
ev->x = (int)p.x;
ev->y = (int)p.y;
}
2007-07-21 19:03:50 +00:00
void create_spawn(vec2 p)
{
// create the event
ev_spawn *ev = (ev_spawn *)events.create(EVENT_SPAWN, sizeof(ev_spawn));
ev->x = (int)p.x;
ev->y = (int)p.y;
}
2007-07-21 21:17:38 +00:00
void create_death(vec2 p)
{
// create the event
ev_death *ev = (ev_death *)events.create(EVENT_DEATH, sizeof(ev_death));
ev->x = (int)p.x;
ev->y = (int)p.y;
}
2007-08-04 17:28:31 +00:00
void create_targetted_sound(vec2 pos, int sound, int target, int loopingflags)
2007-05-22 15:03:32 +00:00
{
if (sound < 0)
return;
// create a sound
2007-08-04 17:28:31 +00:00
ev_sound *ev = (ev_sound *)events.create(EVENT_SOUND, sizeof(ev_sound), target);
2007-05-22 15:03:32 +00:00
ev->x = (int)pos.x;
ev->y = (int)pos.y;
ev->sound = sound | loopingflags;
}
2007-08-04 17:28:31 +00:00
void create_sound(vec2 pos, int sound, int loopingflags)
{
create_targetted_sound(pos, sound, -1, loopingflags);
}
2007-07-13 13:40:04 +00:00
// TODO: should be more general
2007-05-22 15:03:32 +00:00
player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis)
{
// Find other players
entity *ents[64];
vec2 dir = pos1 - pos0;
float radius = length(dir * 0.5f);
vec2 center = pos0 + dir * 0.5f;
2007-07-14 13:09:42 +00:00
const int types[] = {OBJTYPE_PLAYER};
int num = world.find_entities(center, radius, ents, 64, types, 1);
2007-05-22 15:03:32 +00:00
for (int i = 0; i < num; i++)
{
// Check if entity is a player
2007-07-14 13:09:42 +00:00
if (ents[i] != notthis)
2007-05-22 15:03:32 +00:00
{
new_pos = ents[i]->pos;
return (player*)ents[i];
}
}
return 0;
}
// Server hooks
void mods_tick()
{
// clear all events
events.clear();
world.tick();
if(world.paused) // make sure that the game object always updates
gameobj.tick();
2007-05-22 15:03:32 +00:00
2007-07-13 13:40:04 +00:00
if(debug_bots)
2007-05-22 15:03:32 +00:00
{
2007-07-13 13:40:04 +00:00
static int count = 0;
if(count >= 0)
{
count++;
if(count == 10)
{
2007-08-02 20:17:18 +00:00
for(int i = 0; i < debug_bots ; i++)
2007-07-14 13:09:42 +00:00
{
mods_client_enter(MAX_CLIENTS-i-1);
strcpy(players[MAX_CLIENTS-i-1].name, "(bot)");
}
2007-07-13 13:40:04 +00:00
count = -1;
}
}
2007-05-22 15:03:32 +00:00
}
}
void mods_snap(int client_id)
{
world.snap(client_id);
events.snap(client_id);
}
void mods_client_input(int client_id, void *input)
{
if(!world.paused)
{
2007-07-21 19:03:50 +00:00
//players[client_id].previnput = players[client_id].input;
players[client_id].input = *(player_input*)input;
}
2007-05-22 15:03:32 +00:00
}
2007-07-30 07:05:34 +00:00
void send_chat_all(int cid, const char *msg)
{
msg_pack_start(MSG_CHAT, MSGFLAG_VITAL);
msg_pack_int(cid);
msg_pack_string(msg, 512);
msg_pack_end();
server_send_msg(-1);
}
2007-05-22 15:03:32 +00:00
void mods_client_enter(int client_id)
{
players[client_id].init();
2007-05-22 15:03:32 +00:00
players[client_id].client_id = client_id;
world.insert_entity(&players[client_id]);
players[client_id].respawn();
2007-07-14 13:09:42 +00:00
client_info info; // fetch login name
if(server_getclientinfo(client_id, &info))
2007-07-22 09:16:44 +00:00
{
2007-07-14 13:09:42 +00:00
strcpy(players[client_id].name, info.name);
2007-07-22 09:16:44 +00:00
}
2007-07-14 13:09:42 +00:00
else
strcpy(players[client_id].name, "(bot)");
2007-07-22 09:16:44 +00:00
if (gameobj.gametype == GAMETYPE_TDM)
{
// Check which team the player should be on
players[client_id].team = gameobj.getteam(client_id);
}
2007-07-14 13:09:42 +00:00
msg_pack_start(MSG_SETNAME, MSGFLAG_VITAL);
msg_pack_int(client_id);
msg_pack_string(players[client_id].name, 64);
msg_pack_end();
server_send_msg(-1);
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(players[client_id].client_id != -1)
{
msg_pack_start(MSG_SETNAME, MSGFLAG_VITAL);
msg_pack_int(i);
msg_pack_string(players[i].name, 64);
msg_pack_end();
server_send_msg(client_id);
}
}
2007-07-29 22:56:25 +00:00
2007-07-30 07:05:34 +00:00
char buf[512];
sprintf(buf, "%s has joined the game", players[client_id].name);
send_chat_all(-1, buf);
2007-05-22 15:03:32 +00:00
}
void mods_client_drop(int client_id)
{
2007-07-30 07:05:34 +00:00
char buf[512];
sprintf(buf, "%s has left the game", players[client_id].name);
send_chat_all(-1, buf);
2007-07-25 07:24:57 +00:00
dbg_msg("mods", "client drop %d", client_id);
2007-05-22 15:03:32 +00:00
world.remove_entity(&players[client_id]);
players[client_id].client_id = -1;
2007-05-22 15:03:32 +00:00
}
2007-07-14 13:09:42 +00:00
void mods_message(int msg, int client_id)
{
if(msg == MSG_SAY)
{
2007-07-30 07:05:34 +00:00
send_chat_all(client_id, msg_unpack_string());
2007-07-14 13:09:42 +00:00
}
2007-07-22 11:53:15 +00:00
else if (msg == MSG_SWITCHTEAM)
{
// Switch team on given client and kill/respawn him
players[client_id].team = !players[client_id].team;
players[client_id].die(client_id, -1);
players[client_id].score--;
}
2007-07-14 13:09:42 +00:00
}
extern unsigned char internal_data[];
2007-05-22 15:03:32 +00:00
void mods_init()
{
data = load_data_from_memory(internal_data);
2007-05-22 15:03:32 +00:00
col_init(32);
int start, num;
map_get_type(MAPRES_ITEM, &start, &num);
2007-07-13 13:40:04 +00:00
// TODO: this is way more complicated then it should be
2007-05-22 15:03:32 +00:00
for(int i = 0; i < num; i++)
{
mapres_item *it = (mapres_item *)map_get_item(start+i, 0, 0);
int type = -1;
int subtype = -1;
switch(it->type)
{
case ITEM_WEAPON_GUN:
2007-07-23 17:52:04 +00:00
type = POWERUP_WEAPON;
2007-07-22 11:53:15 +00:00
subtype = WEAPON_GUN;
2007-05-22 15:03:32 +00:00
break;
case ITEM_WEAPON_SHOTGUN:
2007-07-23 17:52:04 +00:00
type = POWERUP_WEAPON;
2007-07-22 11:53:15 +00:00
subtype = WEAPON_SHOTGUN;
2007-05-22 15:03:32 +00:00
break;
case ITEM_WEAPON_ROCKET:
2007-07-23 17:52:04 +00:00
type = POWERUP_WEAPON;
2007-07-22 11:53:15 +00:00
subtype = WEAPON_ROCKET;
2007-05-22 15:03:32 +00:00
break;
case ITEM_WEAPON_HAMMER:
2007-07-23 17:52:04 +00:00
type = POWERUP_WEAPON;
2007-07-22 11:53:15 +00:00
subtype = WEAPON_HAMMER;
2007-05-22 15:03:32 +00:00
break;
2007-07-15 13:25:10 +00:00
case ITEM_HEALTH:
2007-07-23 17:52:04 +00:00
type = POWERUP_HEALTH;
2007-05-22 15:03:32 +00:00
break;
2007-07-15 13:25:10 +00:00
case ITEM_ARMOR:
2007-07-23 17:52:04 +00:00
type = POWERUP_ARMOR;
2007-05-22 15:03:32 +00:00
break;
2007-07-22 12:01:20 +00:00
2007-07-22 11:53:15 +00:00
case ITEM_NINJA:
2007-07-23 17:52:04 +00:00
type = POWERUP_NINJA;
2007-07-22 11:53:15 +00:00
subtype = WEAPON_NINJA;
break;
2007-05-22 15:03:32 +00:00
};
2007-07-22 12:01:20 +00:00
if(type != -1)
{
// LOL, the only new in the entire game code
// perhaps we can get rid of it. seams like a stupid thing to have
2007-07-22 12:01:20 +00:00
powerup *ppower = new powerup(type, subtype);
ppower->pos = vec2(it->x, it->y);
}
2007-05-22 15:03:32 +00:00
}
world.insert_entity(&gameobj);
2007-05-22 15:03:32 +00:00
}
void mods_shutdown() {}
void mods_presnap() {}
void mods_postsnap() {}