Files
Descent3/monsterball/monsterball.cpp
2024-04-19 14:58:24 -06:00

2269 lines
57 KiB
C++

/*
* $Logfile: /DescentIII/Main/monsterball/monsterball.cpp $
* $Revision: 1.1.1.1 $
* $Date: 2003-08-26 03:58:29 $
* $Author: kevinb $
*
* Monmsterball Multiplayer game
*
* $Log: not supported by cvs2svn $
*
* 43 9/24/01 3:17p Matt
* Only show results for two teams, since there are always only two teams.
*
* 42 9/24/01 2:28p Matt
* Allowed room for longer team name on results screen.
*
* 41 9/13/01 5:30p Matt
* Added a team member list to the stats display.
*
* 40 10/21/99 2:46p Kevin
* Macintosh merge!
*
* 39 8/17/99 5:53p Jeff
* track ranks on PXO
*
* 38 7/15/99 1:18a Jeff
* fixed up $scores
*
* 37 7/12/99 2:27p Jeff
* fixed PLR to only display the team label for the disconnected list if
* there are people in the list
*
* 36 7/11/99 6:55p Jeff
* fixed PLR so it doesn't go off the screen on long lists and active
* players in the game are shown first
*
* 35 5/23/99 3:04a Jason
* fixed bug with player rankings not being updated correctly
*
* 34 5/13/99 5:08p Ardussi
* changes for compiling on the Mac
*
* 33 5/12/99 11:04p Jeff
* dmfc and multiplayer games now have endian friendly packets (*whew*)
*
* 32 5/12/99 11:29a Jeff
* changed for loop variable for sanity...
*
* 31 5/09/99 6:20a Jeff
* improved Entropy (added sounds, max virii per room). Fixed rendering
* bugs for other multiplayer dlls.
*
* 30 5/08/99 3:54p Jeff
* fixed Post level results screen. Fixed welcome messages. Fixed save
* stats to file generation
*
* 29 5/08/99 5:07a Jeff
* a little touch ups on PLR (needs work)
*
* 28 5/07/99 1:45p Jeff
* don't count dedicated server in team size count
*
* 27 4/30/99 7:37p Jeff
* cool effects when you score
*
* 26 4/28/99 4:24a Jeff
* added safety checks to prevent crashes
*
* 25 4/27/99 3:44p Jeff
* fixed requirement keywords
*
* 24 4/23/99 6:15p Jeff
* fixed double calls to GameClose
*
* 23 4/23/99 12:43p Jeff
* forgot to call CloseGame
*
* 22 4/22/99 7:15p Jeff
* changed requirement strings
*
* 21 4/21/99 5:34p Jeff
* added requirements
*
* 20 4/19/99 6:23p Jeff
* Linux compile
*
* 19 3/31/99 5:49p Jeff
* fixed bug of displaying icon accidently
*
* 18 3/27/99 4:53p Jeff
* player rankings in multiplayer games implemented. Fixed join message
* so it doesn't get cut off
*
* 17 3/19/99 5:42p Jeff
* high res font fixups
*
* 16 3/19/99 12:54p Jeff
* base support for requesting the number of teams for a multiplayer game
*
* 15 3/17/99 12:23p Jeff
* converted DMFC to be COM interface
*
* 14 2/11/99 6:09p Jeff
* localized
*
* 13 2/11/99 12:49a Jeff
* changed names of exported variables
*
* 12 2/10/99 1:48p Matt
* Changed object handle symbolic constants
*
* 11 2/09/99 3:32p Jeff
* table file parser takes quotes strings for force keywords
*
* 10 2/05/99 7:03p Jeff
* table file parsing macros put in
*
* 9 1/31/99 7:26p Matt
* Renamed a bunch of functions to have HUD capitalized
*
* 8 1/24/99 8:32p Jeff
* a bunch of updates to get 100% up to date, added individual scoring.
*
* 7 1/21/99 11:16p Jeff
* exported vecmat functions
*
* 6 1/20/99 8:05p Jeff
* fixed some bugs
*
* 5 1/20/99 2:28p Jeff
*
* 4 1/19/99 9:54p Jeff
* improvements added...very complete
*
* 3 1/19/99 5:34p Jeff
* updated monsterball
*
* 2 1/18/99 12:06a Jeff
* updated to get 90% of base functionality in
*
* $NoKeywords: $
*/
#include "gamedll_header.h"
#include "idmfc.h"
#include "monsterball.h"
#include <string.h>
#include "monsterstr.h"
#ifdef MACINTOSH
#include <stdlib.h>
#endif
IDMFC *DMFCBase = NULL;
IDmfcStats *dstat = NULL;
room *dRooms;
object *dObjects;
player *dPlayers;
vis_effect *dVisEffects;
//////////////////////////////////
// defines
#define SPID_NEWPLAYER 0
#define SPID_HITINFO 1
#define MONSTERBALL_ID_NAME "Monsterball"
#define MONSTERBALL_SND_SCORE "Extra life"
#define MONSTERBALL_SND_PICKUP "IGotIt"
#define MONSTERBALL_SND_HIT "Monsterball Hit"
#define MONSTERBALL_LOSE "Drop unknown "
/*
$$TABLE_SOUND "Extra life"
$$TABLE_GENERIC "Monsterball"
$$TABLE_SOUND "IGotIt"
$$TABLE_SOUND "Monsterball Hit"
$$TABLE_SOUND "Drop unknown "
*/
#define SCORE_DRAGIN 3
#define SCORE_HITIN 1
#define MAX_MONSTERBALL_VEL 120.0f
//////////////////////////////////
// Structs
typedef struct{
int Score[2];
int BadScore[2];
}tPlayerStat;
int pack_pstat(tPlayerStat *user_info,ubyte *data)
{
int count = 0;
MultiAddInt(user_info->Score[0],data,&count);
MultiAddInt(user_info->Score[1],data,&count);
MultiAddInt(user_info->BadScore[0],data,&count);
MultiAddInt(user_info->BadScore[1],data,&count);
return count;
}
int unpack_pstat(tPlayerStat *user_info,ubyte *data)
{
int count = 0;
user_info->Score[0] = MultiGetInt(data,&count);
user_info->Score[1] = MultiGetInt(data,&count);
user_info->BadScore[0] = MultiGetInt(data,&count);
user_info->BadScore[1] = MultiGetInt(data,&count);
return count;
}
typedef struct{
vector rotvel;
vector velocity;
vector pos;
matrix orient;
float mass;
float size;
}weapon_collide_info;
struct{
int id,room;
int objhandle;
int owner_handle;
int icon;
vector pos;
}Monsterball_info;
//////////////////////////////////
// Globals
int SortedPLRPlayers[DLLMAX_TEAMS][MAX_PLAYER_RECORDS];
int TeamScores[DLLMAX_TEAMS];
int SortedTeams[DLLMAX_TEAMS];
int SortedPlayers[MAX_PLAYER_RECORDS];
int NumOfTeams = 2;
int WhoJustScored = -1,WhoJustScoredTimer=-1;
int GoalRooms[DLLMAX_TEAMS];
int Highlight_bmp;
int LastHitPnum = -1;
bool players_sorted = false; //the Sorted*[] have been sorted
bool DisplayPowerBBlink = true;
bool DisplayScoreBlink = true;
bool DisplayScoreScreen = false;
bool display_my_welcome = false;
bool monsterball_info_set = false;
///////////////////////////////////////////////
//localization info
char **StringTable;
int StringTableSize = 0;
char *_ErrorString = "Missing String";
char *GetStringFromTable(int d){if( (d<0) || (d>=StringTableSize) ) return _ErrorString; else return StringTable[d];}
///////////////////////////////////////////////
//////////////////////////////////
// Prototypes
void DisplayHUDScores(struct tHUDItem *hitem);
void DisplayStats(void);
void GetGameStartPacket(ubyte *data);
void SendGameStartPacket(int pnum);
bool GetMonsterballInfo(int id);
void SortTeams(void);
void DisplayWelcomeMessage(int player_num);
void OnTimerScore(void);
void OnTimer(void);
void OnTimerScoreKill(void);
void OnTimerKill(void);
void OnTimerRegen(void);
void OnTimerRegenKill(void);
void SaveStatsToFile(char *filename);
void SortPlayerSlots(int *sorted_list,int count);
void SendLastHitInfo(void);
void GetLastHitInfo(ubyte *data);
void HandlePickupPowerball(object *owner);
void HandleLosePowerball(bool play_sound);
void HandleMonsterballCollideWithObject(object *ball,object *player,vector *point,vector *normal);
void HandleMonsterballCollideWithWeapon(object *ball,weapon_collide_info *winfo,vector *point,vector *normal);
void bump_object(object *object0, vector *rotvel, vector *velocity, vector *pos, matrix *orient, float mass, float size,vector *collision_point,vector *collision_normal,float rot_scale,float vel_scale);
void DoMonsterballScoreEffect(void);
void OnClientPlayerEntersGame(int player_num);
bool ValidateOwner(int *pnum,object **obj);
void DetermineScore(int precord_num,int column_num,char *buffer,int buffer_size)
{
player_record *pr = DMFCBase->GetPlayerRecord(precord_num);
if(!pr || pr->state==STATE_EMPTY){
buffer[0] = '\0';
return;
}
tPlayerStat *stat = (tPlayerStat *)pr->user_info;
if(column_num==2){
sprintf(buffer,"%d",(stat)?stat->Score[DSTAT_LEVEL]:0);
}else{
sprintf(buffer,"%d",(stat)?stat->BadScore[DSTAT_LEVEL]:0);
}
}
void TeamScoreCallback(int team,char *buffer,int buffer_size)
{
ASSERT(team>=0 && team<DLLMAX_TEAMS);
sprintf(buffer," %d",TeamScores[team]);
}
void ShowStatBitmap(int precord_num,int column_num,int x,int y,int w,int h,ubyte alpha_to_use)
{
player_record *pr = DMFCBase->GetPlayerRecord(precord_num);
if(!pr || pr->state!=STATE_INGAME)
return;
int pnum;
object *owner;
if(!ValidateOwner(&pnum,&owner))
return;
//the owner is still valid
if(pnum == pr->pnum){
DLLRenderHUDQuad(x+2,y,10,10,0,0,1,1,Monsterball_info.icon,alpha_to_use);
}
}
#ifdef MACINTOSH
#pragma export on
#endif
#define NUM_TEAMS 2
// This function gets called by the game when it wants to learn some info about the game
void DLLFUNCCALL DLLGetGameInfo (tDLLOptions *options)
{
options->flags = DOF_MAXTEAMS|DOF_MINTEAMS;
options->max_teams = NUM_TEAMS;
options->min_teams = NUM_TEAMS;
strcpy(options->game_name,TXT_GAMENAME);
strcpy(options->requirements,"MINGOALS2,GOALPERTEAM,SPEC1");
}
// Initializes the game function pointers
void DLLFUNCCALL DLLGameInit (int *api_func,ubyte *all_ok,int num_teams_to_use)
{
*all_ok = 1;
DMFCBase = CreateDMFC();
if(!DMFCBase)
{
*all_ok = 0;
return;
}
dstat = CreateDmfcStats();
if(!dstat)
{
*all_ok = 0;
return;
}
DMFCBase->LoadFunctions(api_func);
//Setup event handlers
DMFCBase->Set_OnCanChangeTeam(OnCanChangeTeam);
DMFCBase->Set_OnClientPlayerDisconnect(OnClientPlayerDisconnect);
DMFCBase->Set_OnPlayerEntersObserver(OnPlayerEntersObserver);
DMFCBase->Set_OnClientPlayerKilled(OnClientPlayerKilled);
DMFCBase->Set_OnServerObjectShieldsChanged(OnServerObjectShieldsChanged);
DMFCBase->Set_OnGameStateRequest(OnGameStateRequest);
DMFCBase->Set_OnServerGameCreated(OnServerGameCreated);
DMFCBase->Set_OnClientLevelStart(OnClientLevelStart);
DMFCBase->Set_OnPlayerConnect(OnPlayerConnect);
DMFCBase->Set_OnInterval(OnInterval);
DMFCBase->Set_OnHUDInterval(OnHUDInterval);
DMFCBase->Set_OnKeypress(OnKeypress);
DMFCBase->Set_OnServerCollide(OnServerCollide);
DMFCBase->Set_OnClientCollide(OnClientCollide);
DMFCBase->Set_OnServerPlayerChangeSegment(OnServerPlayerChangeSegment);
DMFCBase->Set_OnClientPlayerChangeSegment(OnClientPlayerChangeSegment);
DMFCBase->Set_OnServerObjectChangeSegment(OnServerObjectChangeSegment);
DMFCBase->Set_OnClientObjectChangeSegment(OnClientObjectChangeSegment);
DMFCBase->Set_OnSaveStatsToFile(OnSaveStatsToFile);
DMFCBase->Set_OnLevelEndSaveStatsToFile(OnLevelEndSaveStatsToFile);
DMFCBase->Set_OnDisconnectSaveStatsToFile(OnDisconnectSaveStatsToFile);
DMFCBase->Set_OnPLRInterval(OnPLRInterval);
DMFCBase->Set_OnPLRInit(OnPLRInit);
DMFCBase->Set_OnPrintScores(OnPrintScores);
DMFCBase->Set_OnClientPlayerEntersGame(OnClientPlayerEntersGame);
//setup arrays
dRooms = DMFCBase->GetRooms();
dObjects = DMFCBase->GetObjects();
dPlayers = DMFCBase->GetPlayers();
dVisEffects = DMFCBase->GetVisEffectArray();
netgame_info *Netgame = DMFCBase->GetNetgameInfo();
Netgame->flags |= (NF_TRACK_RANK);
DMFCBase->GameInit(NumOfTeams);
DLLCreateStringTable("monster.str",&StringTable,&StringTableSize);
DLLmprintf((0,"%d strings loaded from string table\n",StringTableSize));
//add the death and suicide messages
DMFCBase->AddDeathMessage(TXT_KILLEDA,true);
DMFCBase->AddSuicideMessage(TXT_SUICIDEA);
//setup the Playerstats struct so DMFC can handle it automatically when a new player enters the game
DMFCBase->SetupPlayerRecord(sizeof(tPlayerStat),(int (*)(void *,ubyte *))pack_pstat,(int (*)(void *,ubyte *))unpack_pstat);
//register special packet receivers
DMFCBase->RegisterPacketReceiver(SPID_NEWPLAYER,GetGameStartPacket);
DMFCBase->RegisterPacketReceiver(SPID_HITINFO,GetLastHitInfo);
DMFCBase->SetNumberOfTeams(NumOfTeams);
DMFCBase->AddHUDItemCallback(HI_TEXT,DisplayHUDScores);
Monsterball_info.icon = DLLbm_AllocLoadFileBitmap("PowerballIcon.ogf",0);
if(Monsterball_info.icon==-1)
Monsterball_info.icon = BAD_BITMAP_HANDLE;
DisplayScoreScreen = false;
//TableFileAdd("Monsterball.Gam");
Highlight_bmp = DLLbm_AllocBitmap(32,32,0);
if(Highlight_bmp>BAD_BITMAP_HANDLE){
ushort *data = DLLbm_data(Highlight_bmp,0);
if(!data){
//bail on out of here
*all_ok = 0;
return;
}
for(int x=0;x<32*32;x++){
data[x] = GR_RGB16(50,50,50)|OPAQUE_FLAG;
}
}
// Initialize the Stats Manager
// ----------------------------
tDmfcStatsInit tsi;
tDmfcStatsColumnInfo pl_col[8];
char gname[20];
strcpy(gname,TXT_STATGAMENAME);
tsi.flags = DSIF_SHOW_PIC|DSIF_SHOW_OBSERVERICON|DSIF_SEPERATE_BY_TEAM;
tsi.cColumnCountDetailed = 0;
tsi.cColumnCountPlayerList = 8;
tsi.clbDetailedColumnBMP = NULL;
tsi.clbDetailedColumn = NULL;
tsi.clbPlayerColumn = DetermineScore;
tsi.clbPlayerColumnBMP = ShowStatBitmap;
tsi.DetailedColumns = NULL;
tsi.GameName = gname;
tsi.MaxNumberDisplayed = NULL;
tsi.PlayerListColumns = pl_col;
tsi.SortedPlayerRecords = SortedPlayers;
tsi.clTeamLine = TeamScoreCallback;
pl_col[0].color_type = DSCOLOR_TEAM;
pl_col[0].title[0] = '\0';
pl_col[0].type = DSCOL_BMP;
pl_col[0].width = 15;
pl_col[1].color_type = DSCOLOR_TEAM;
strcpy(pl_col[1].title,TXT_PILOT);
pl_col[1].type = DSCOL_PILOT_NAME;
pl_col[1].width = 110;
pl_col[2].color_type = DSCOLOR_TEAM;
strcpy(pl_col[2].title,TXT_POINTS);
pl_col[2].type = DSCOL_CUSTOM;
pl_col[2].width = 51;
pl_col[3].color_type = DSCOLOR_TEAM;
strcpy(pl_col[3].title,TXT_BLUNDERS);
pl_col[3].type = DSCOL_CUSTOM;
pl_col[3].width = 64;
pl_col[4].color_type = DSCOLOR_TEAM;
strcpy(pl_col[4].title,TXT_KILLSSHORT);
pl_col[4].type = DSCOL_KILLS_LEVEL;
pl_col[4].width = 41;
pl_col[5].color_type = DSCOLOR_TEAM;
strcpy(pl_col[5].title,TXT_DEATHSSHORT);
pl_col[5].type = DSCOL_DEATHS_LEVEL;
pl_col[5].width = 41;
pl_col[6].color_type = DSCOLOR_TEAM;
strcpy(pl_col[6].title,TXT_SUICIDESSHORT);
pl_col[6].type = DSCOL_SUICIDES_LEVEL;
pl_col[6].width = 41;
pl_col[7].color_type = DSCOLOR_TEAM;
strcpy(pl_col[7].title,TXT_PING);
pl_col[7].type = DSCOL_PING;
pl_col[7].width = 40;
dstat->Initialize(&tsi);
}
// Called when the DLL is shutdown
void DLLFUNCCALL DLLGameClose ()
{
if(Monsterball_info.icon>BAD_BITMAP_HANDLE)
DLLbm_FreeBitmap(Monsterball_info.icon);
DLLDestroyStringTable(StringTable,StringTableSize);
if(dstat)
{
dstat->DestroyPointer();
dstat = NULL;
}
if(DMFCBase)
{
DMFCBase->GameClose();
DMFCBase->DestroyPointer();
DMFCBase = NULL;
}
}
bool OnCanChangeTeam(int pnum,int newteam)
{
if(!DMFCBase->OnCanChangeTeam(pnum,newteam))
return false;
int p;
if(!ValidateOwner(&p,NULL))
return true;
if(p == pnum)
return false;
return true;
}
void OnHUDInterval(void)
{
dstat->DoFrame();
DMFCBase->DisplayOutrageLogo();
DMFCBase->OnHUDInterval();
}
void OnInterval(void)
{
SortPlayerSlots(SortedPlayers,MAX_PLAYER_RECORDS);
players_sorted = true;
SortTeams();
if(DMFCBase->GetLocalRole()==LR_SERVER){
//make sure velocity of the ball isn't more than MAX_MONSTERBALL_VEL
object *ball;
if(!DLLObjGet(Monsterball_info.objhandle,&ball)){
//blah!
FatalError("MONSTERBALL IS MISSING\n");
}
float mag = DLLvm_GetMagnitudeFast(&ball->mtype.phys_info.velocity);
if(mag>MAX_MONSTERBALL_VEL){
ball->mtype.phys_info.velocity /= mag;
ball->mtype.phys_info.velocity *= MAX_MONSTERBALL_VEL;
}
}
DMFCBase->OnInterval();
}
void OnKeypress(int key)
{
dllinfo *Data = DMFCBase->GetDLLInfoCallData();
switch(key){
case K_F7:
DisplayScoreScreen = !DisplayScoreScreen;
DMFCBase->EnableOnScreenMenu(false);
dstat->Enable(DisplayScoreScreen);
break;
case K_PAGEDOWN:
if(DisplayScoreScreen){
dstat->ScrollDown();
Data->iRet = 1;
}
break;
case K_PAGEUP:
if(DisplayScoreScreen){
dstat->ScrollUp();
Data->iRet = 1;
}
break;
case K_F6:
DisplayScoreScreen = false;
dstat->Enable(false);
break;
case K_ESC:
if(DisplayScoreScreen){
dstat->Enable(false);
DisplayScoreScreen = false;
Data->iRet = 1;
}
break;
}
DMFCBase->OnKeypress(key);
}
// The server has just started, so clear out all the stats and game info
void OnServerGameCreated(void)
{
DMFCBase->OnServerGameCreated();
tPlayerStat *stat;
player_record *pr;
int i;
for(i=0;i<MAX_PLAYER_RECORDS;i++){
pr = DMFCBase->GetPlayerRecord(i);
if(!pr)
continue;
stat = (tPlayerStat *)pr->user_info;
if(stat){
stat->Score[DSTAT_LEVEL] = stat->Score[DSTAT_OVERALL] = 0;
stat->BadScore[DSTAT_LEVEL] = stat->BadScore[DSTAT_OVERALL] = 0;
}
}
for(i=0;i<NumOfTeams;i++){
TeamScores[i] = 0;
}
}
void OnServerCollide(object *me_obj,object *it_obj,vector *point,vector *normal)
{
if( !me_obj || !it_obj ){
DMFCBase->OnServerCollide(me_obj,it_obj,point,normal);
return;
}
// check to see if the collision was with the Monsterball
if(me_obj->handle==Monsterball_info.objhandle){
LastHitPnum = -1;
switch(it_obj->type){
case OBJ_PLAYER:
{
if(DMFCBase->IsPlayerAlive(it_obj->id)){
LastHitPnum = it_obj->id;
//call the event on the client so they can do any physics sim
DMFCBase->CallClientEvent(EVT_CLIENT_GAMECOLLIDE,DMFCBase->GetMeObjNum(),DMFCBase->GetItObjNum(),-1,true);
DMFCBase->CallOnClientCollide(me_obj,it_obj,point,normal);
}
}break;
case OBJ_WEAPON:
{
object *parent;
DLLGetUltimateParentForObject(&parent,it_obj);
if(parent->type == OBJ_PLAYER){
LastHitPnum = parent->id;
}
weapon_collide_info winfo;
winfo.rotvel.x = it_obj->mtype.phys_info.rotvel.x;
winfo.rotvel.y = it_obj->mtype.phys_info.rotvel.y;
winfo.rotvel.z = it_obj->mtype.phys_info.rotvel.z;
winfo.velocity.x = it_obj->mtype.phys_info.velocity.x;
winfo.velocity.y = it_obj->mtype.phys_info.velocity.y;
winfo.velocity.z = it_obj->mtype.phys_info.velocity.z;
winfo.pos.x = it_obj->pos.x;
winfo.pos.y = it_obj->pos.y;
winfo.pos.z = it_obj->pos.z;
winfo.orient.fvec.x = it_obj->orient.fvec.x;
winfo.orient.fvec.y = it_obj->orient.fvec.y;
winfo.orient.fvec.z = it_obj->orient.fvec.z;
winfo.orient.uvec.x = it_obj->orient.uvec.x;
winfo.orient.uvec.y = it_obj->orient.uvec.y;
winfo.orient.uvec.z = it_obj->orient.uvec.z;
winfo.orient.rvec.x = it_obj->orient.rvec.x;
winfo.orient.rvec.y = it_obj->orient.rvec.y;
winfo.orient.rvec.z = it_obj->orient.rvec.z;
winfo.mass = it_obj->mtype.phys_info.mass;
winfo.size = it_obj->size;
HandleMonsterballCollideWithWeapon(me_obj,&winfo,point,normal);
}break;
}
}
//check to see if the collision was between a player and another player, who has the powerball
if(me_obj->type == OBJ_PLAYER && (it_obj->type==OBJ_PLAYER||it_obj->type==OBJ_WEAPON)){
if(it_obj->type==OBJ_PLAYER && !DMFCBase->IsPlayerAlive(it_obj->id) ){
//the collided player isn't alive
}else{
int p;
if(ValidateOwner(&p,NULL)){
if(p==me_obj->id){
//the player has the powerball
DMFCBase->CallClientEvent(EVT_CLIENT_GAMECOLLIDE,DMFCBase->GetMeObjNum(),DMFCBase->GetItObjNum(),-1,true);
DMFCBase->CallOnClientCollide(me_obj,it_obj,point,normal);
}
}
}
}
DMFCBase->OnServerCollide(me_obj,it_obj,point,normal);
}
void OnClientPlayerDisconnect(int player_num)
{
int p;
if(ValidateOwner(&p,NULL)){
if(p==player_num){
HandleLosePowerball(true);
}
}
DMFCBase->OnClientPlayerDisconnect(player_num);
}
void OnPlayerEntersObserver(int pnum,object *piggy)
{
int p;
if(ValidateOwner(&p,NULL)){
if(p==pnum){
HandleLosePowerball(true);
}
}
DMFCBase->OnPlayerEntersObserver(pnum,piggy);
}
void OnClientPlayerKilled(object *killer_obj,int victim_pnum)
{
int p;
if(ValidateOwner(&p,NULL)){
if(p==victim_pnum){
HandleLosePowerball(true);
}
}
DMFCBase->OnClientPlayerKilled(killer_obj,victim_pnum);
}
void HandleLosePowerball(bool play_sound)
{
static int sound = -1;
object *owner;
if(!ValidateOwner(NULL,&owner))
return;
object *ball;
if(!DLLObjGet(Monsterball_info.objhandle,&ball)){
//blah!
FatalError("MONSTERBALL IS MISSING\n");
}
if(play_sound){
if(sound==-1)
sound=DLLFindSoundName(IGNORE_TABLE(MONSTERBALL_LOSE));
if(sound!=-1)
DLLPlay2dSound(sound);
}
if(DMFCBase->GetLocalRole()==LR_SERVER)
DLLUnattachFromParent(ball);
Monsterball_info.owner_handle = OBJECT_HANDLE_NONE;
}
void HandlePickupPowerball(object *owner)
{
static int sound = -1;
if(sound==-1)
sound=DLLFindSoundName(IGNORE_TABLE(MONSTERBALL_SND_PICKUP));
if(sound!=-1)
DLLPlay2dSound(sound);
if(DMFCBase->GetLocalRole()==LR_SERVER){
//if it was a player, attach the ball to the player
object *bobj;
if(!DLLObjGet(Monsterball_info.objhandle,&bobj)){
FatalError("Monster Ball not found\n");
}
bool ret = DLLAttachObject(owner,0,bobj,0,true);
if(!ret){
//blah!
mprintf((0,"COULDN'T ATTACH MONSTERBALL TO PLAYER!!!!!!\n"));
}
}
Monsterball_info.owner_handle = owner->handle;
}
void OnClientCollide(object *me_obj,object *it_obj,vector *point,vector *normal)
{
//three things can happen in here
// 1) The collision is between the ball (with no owner) and a player
// 2) The collision is between the owner and another player or weapon
// 3) The collision is between the ball and a robot
if( me_obj && me_obj->type==OBJ_PLAYER){
//case 2: me_obj = owner
//Result: The owner is to lose the monsterball
HandleLosePowerball(true);
DMFCBase->OnClientCollide(me_obj,it_obj,point,normal);
return;
}
if( me_obj && me_obj->type==OBJ_ROBOT){
if(it_obj->type==OBJ_ROBOT){
//case 3: me_obj = monsterball it_obj = robot
if(DMFCBase->GetLocalRole()==LR_SERVER){
//do the physics on the Monsterball
HandleMonsterballCollideWithObject(me_obj,it_obj,point,normal);
}
}
if(it_obj->type==OBJ_PLAYER){
//case 1: me_obj = monsterball it_obj = player
//Result: The player is to pickup the monsterball (as long as it has no owner)
int p;
if(!ValidateOwner(&p,NULL) && p!=it_obj->id){
ASSERT(it_obj->type==OBJ_PLAYER);
//HandlePickupPowerball(it_obj);
HandleMonsterballCollideWithObject(me_obj,it_obj,point,normal);
}
}
DMFCBase->OnClientCollide(me_obj,it_obj,point,normal);
return;
}
DMFCBase->OnClientCollide(me_obj,it_obj,point,normal);
}
void OnServerObjectShieldsChanged(object *obj,float amount)
{
if(!obj)
return;
if(obj->handle == Monsterball_info.objhandle){
obj->shields += amount;
}
}
void OnServerObjectChangeSegment(object *obj,int newseg,int oldseg)
{
if(!obj){
DMFCBase->OnServerObjectChangeSegment(obj,newseg,oldseg);
return;
}
if(obj->type==OBJ_ROBOT && obj->handle==Monsterball_info.objhandle){
//this is the monster ball, see if it's going into a goal room
bool is_goal = false;
for(int i=0;i<NumOfTeams;i++){
if(newseg==GoalRooms[i]){
is_goal = true;
break;
}
}
if(is_goal && !ValidateOwner(NULL,NULL)){
//it's a team goal
SendLastHitInfo();
DMFCBase->CallClientEvent(EVT_CLIENT_GAMEOBJCHANGESEG,DMFCBase->GetMeObjNum(),DMFCBase->GetItObjNum(),-1,true);
DMFCBase->CallOnClientObjectChangeSegment(obj,newseg,oldseg);
}
}
DMFCBase->OnServerObjectChangeSegment(obj,newseg,oldseg);
}
void OnClientObjectChangeSegment(object *obj,int newseg,int oldseg)
{
static int sound = -1;
if(!obj){
DMFCBase->OnClientObjectChangeSegment(obj,newseg,oldseg);
FatalError ("INVALID OBJECT PASSED INTO OnClientObjectChangeSegment()");
return;
}
int team = -1;
for(int i=0;i<NumOfTeams;i++){
if(newseg==GoalRooms[i]){
team = i;
break;
}
}
if(team<0 || team>=NumOfTeams){
FatalError ("NOT A GOAL ROOM\n");
return;
}
if(sound==-1)
sound=DLLFindSoundName(IGNORE_TABLE(MONSTERBALL_SND_SCORE));
if(sound!=-1)
DLLPlay2dSound(sound,MAX_GAME_VOLUME*0.66f);
DoMonsterballScoreEffect();
DLLvm_MakeVectorZero(&obj->mtype.phys_info.velocity);
DLLvm_MakeIdentity(&obj->orient);
DLLObjSetPos(obj,&Monsterball_info.pos,Monsterball_info.room,NULL,false);
if(!DMFCBase->CheckPlayerNum(LastHitPnum)){
if(SCORE_HITIN==1){
DLLAddHUDMessage(TXT_SCOREONE,DMFCBase->GetTeamString(team));
}else{
DLLAddHUDMessage(TXT_SCOREMULTI,DMFCBase->GetTeamString(team),SCORE_HITIN);
}
}else{
int last_hit_team = dPlayers[LastHitPnum].team;
char *callsign = dPlayers[LastHitPnum].callsign;
tPlayerStat *stat = (tPlayerStat *)DMFCBase->GetPlayerRecordData(LastHitPnum);
if(last_hit_team!=team){
//he scored for the other team!!!
if(SCORE_HITIN==1){
DLLAddHUDMessage(TXT_BLUNDERSCORE,callsign,DMFCBase->GetTeamString(team));
}else{
DLLAddHUDMessage(TXT_BLUNDERMULTISCORE,callsign,SCORE_HITIN,DMFCBase->GetTeamString(team));
}
if(stat){
stat->BadScore[DSTAT_LEVEL]++;
stat->BadScore[DSTAT_OVERALL]++;
}
}else{
//good score!
if(SCORE_HITIN==1){
DLLAddHUDMessage(TXT_PLAYERSCORE,callsign,DMFCBase->GetTeamString(team));
}else{
DLLAddHUDMessage(TXT_PLAYERMULTISCORE,callsign,DMFCBase->GetTeamString(team),SCORE_HITIN);
}
if(stat){
stat->Score[DSTAT_LEVEL]++;
stat->Score[DSTAT_OVERALL]++;
}
}
}
TeamScores[team]+= SCORE_HITIN;
//do killgoal check
int goal;
if((DMFCBase->GetScoreLimit(&goal))&&(DMFCBase->GetLocalRole()==LR_SERVER)){
if(TeamScores[team]>=goal)
DMFCBase->EndLevel();
}
//Set a Timer to display
if(WhoJustScoredTimer!=-1)
DMFCBase->KillTimer(WhoJustScoredTimer);
WhoJustScoredTimer = DMFCBase->SetTimerInterval(OnTimerScore,0.5f,5.0f,OnTimerScoreKill);
WhoJustScored = team;
DMFCBase->OnClientObjectChangeSegment(obj,newseg,oldseg);
}
void OnServerPlayerChangeSegment(int player_num,int newseg,int oldseg)
{
if(player_num==-1)
return;
if(newseg==GoalRooms[DMFCBase->GetPlayerTeam(player_num)]){
//check to see if a player dragged the ball into the goal
int p;
if(ValidateOwner(&p,NULL)){
// a player currently has the monsterball
if(p==player_num){
//it's this player
DMFCBase->CallClientEvent(EVT_CLIENT_GAMEPLAYERCHANGESEG,DMFCBase->GetMeObjNum(),DMFCBase->GetItObjNum(),-1);
DMFCBase->CallOnClientPlayerChangeSegment(player_num,newseg,oldseg);
}
}
}
DMFCBase->OnServerPlayerChangeSegment(player_num,newseg,oldseg);
}
void OnClientPlayerChangeSegment(int player_num,int newseg,int oldseg)
{
// If we get here, than a player dragged the powerball into their goal
static int sound = -1;
object *bobj;
int p;
//if we got here than the player with the Monsterball has entered his team's goal!
if(DMFCBase->IsPlayerDedicatedServer(player_num))
return; //dedicated server
if( !ValidateOwner(&p,NULL) ){
return;
}
if(p!=player_num)
return;
HandleLosePowerball(false);
if(!DLLObjGet(Monsterball_info.objhandle,&bobj)){
FatalError("MonsterBall Is Missing!\n");
}
DLLObjSetPos(bobj,&Monsterball_info.pos,Monsterball_info.room,NULL,false);
if(SCORE_DRAGIN==1)
DLLAddHUDMessage(TXT_SCOREONE,DMFCBase->GetTeamString(dPlayers[player_num].team));
else
DLLAddHUDMessage(TXT_SCOREMULTI,DMFCBase->GetTeamString(dPlayers[player_num].team),SCORE_DRAGIN);
TeamScores[dPlayers[player_num].team]+= SCORE_DRAGIN;
if(sound==-1)
sound=DLLFindSoundName(IGNORE_TABLE(MONSTERBALL_SND_SCORE));
if(sound!=-1)
DLLPlay2dSound(sound);
//do killgoal check
int goal;
if((DMFCBase->GetScoreLimit(&goal))&&(DMFCBase->GetLocalRole()==LR_SERVER)){
if(TeamScores[dPlayers[player_num].team]>=goal)
DMFCBase->EndLevel();
}
//Set a Timer to display
if(WhoJustScoredTimer!=-1)
DMFCBase->KillTimer(WhoJustScoredTimer);
WhoJustScoredTimer = DMFCBase->SetTimerInterval(OnTimerScore,0.5f,5.0f,OnTimerScoreKill);
WhoJustScored = dPlayers[player_num].team;
}
// The server has started a new level, so clear out any scores needed to be reset
void OnClientLevelStart(void)
{
DMFCBase->OnClientLevelStart();
tPlayerStat *stat;
int i;
player_record *pr;
for(i=0;i<MAX_PLAYER_RECORDS;i++){
pr = DMFCBase->GetPlayerRecord(i);
if(pr)
stat = (tPlayerStat *)pr->user_info;
else
stat = NULL;
if(stat){
stat->Score[DSTAT_LEVEL] = 0;
stat->BadScore[DSTAT_LEVEL] = 0;
}
}
for(i=0;i<NumOfTeams;i++){
TeamScores[i] = 0;
GoalRooms[i] = DLLGetGoalRoomForTeam(i);
}
LastHitPnum = -1;
DLLMultiPaintGoalRooms();
DLLmprintf((0,"Getting Monsterball info\n"));
if(!GetMonsterballInfo(DLLFindObjectIDName(IGNORE_TABLE(MONSTERBALL_ID_NAME)))){
FatalError("Error finding Monsterball room\n");
}
if(DMFCBase->GetLocalRole()==LR_SERVER){
int objnum = DLLObjCreate(OBJ_ROBOT,Monsterball_info.id,Monsterball_info.room,&Monsterball_info.pos,NULL);
if(objnum==-1)
FatalError("Unable to create Monsterball");
dObjects[objnum].mtype.phys_info.rotdrag = 5.0f;
DLLMultiSendObject(&dObjects[objnum],1);
Monsterball_info.objhandle = dObjects[objnum].handle;
Monsterball_info.owner_handle = OBJECT_HANDLE_NONE;
monsterball_info_set = true;
}else{
DMFCBase->RequestGameState();
}
players_sorted = false;
}
// A New Player has entered the game, so we want to send him a game status packet that
// has information about the game
void OnGameStateRequest(int player_num)
{
SendGameStartPacket(player_num);
DMFCBase->OnGameStateRequest(player_num);
}
// A new player has entered the game, zero their stats out
void OnPlayerConnect(int player_num)
{
tPlayerStat *stat = (tPlayerStat *)DMFCBase->GetPlayerRecordData(player_num);
if(stat){
stat->Score[DSTAT_LEVEL] = 0;
stat->Score[DSTAT_OVERALL] = 0;
stat->BadScore[DSTAT_LEVEL] = 0;
stat->BadScore[DSTAT_OVERALL] = 0;
}
DMFCBase->OnPlayerConnect(player_num);
}
// A new player has entered the game, zero there stats out
void OnClientPlayerEntersGame(int player_num)
{
DMFCBase->OnClientPlayerEntersGame(player_num);
if(player_num!=DMFCBase->GetPlayerNum())
DisplayWelcomeMessage(player_num);
else
display_my_welcome = true;
}
bool compare_slots(int a,int b)
{
int ascore,bscore;
player_record *apr,*bpr;
tPlayerStat *astat,*bstat;
apr = DMFCBase->GetPlayerRecord(a);
bpr = DMFCBase->GetPlayerRecord(b);
if( !apr )
return true;
if( !bpr )
return false;
if( apr->state==STATE_EMPTY )
return true;
if( bpr->state==STATE_EMPTY )
return false;
astat = (tPlayerStat *)apr->user_info;
bstat = (tPlayerStat *)bpr->user_info;
if( (apr->state==STATE_INGAME) && (bpr->state==STATE_INGAME) ){
//both players were in the game
ascore = (astat)?astat->Score[DSTAT_LEVEL]:0;
bscore = (bstat)?bstat->Score[DSTAT_LEVEL]:0;
return (ascore<bscore);
}
if( (apr->state==STATE_INGAME) && (bpr->state==STATE_DISCONNECTED) ){
//apr gets priority since he was in the game on exit
return false;
}
if( (apr->state==STATE_DISCONNECTED) && (bpr->state==STATE_INGAME) ){
//bpr gets priority since he was in the game on exit
return true;
}
//if we got here then both players were disconnected
ascore = (astat)?astat->Score[DSTAT_LEVEL]:0;
bscore = (bstat)?bstat->Score[DSTAT_LEVEL]:0;
return (ascore<bscore);
}
void SortPlayerSlots(int *sorted_list,int count)
{
int tempsort[MAX_PLAYER_RECORDS];
int i,t,j;
for(i=0;i<MAX_PLAYER_RECORDS;i++){
tempsort[i] = i;
}
for(i=1;i<=MAX_PLAYER_RECORDS-1;i++){
t=tempsort[i];
// Shift elements down until
// insertion point found.
for(j=i-1;j>=0 && compare_slots(tempsort[j],t); j--){
tempsort[j+1] = tempsort[j];
}
// insert
tempsort[j+1] = t;
}
//copy the array over
memcpy(sorted_list,tempsort,count*sizeof(int));
}
void OnPLRInit(void)
{
monsterball_info_set = false; //for the next level
SortPlayerSlots(SortedPlayers,MAX_PLAYER_RECORDS);
//Now fill in the final structure of sorted names
int TeamCount[DLLMAX_TEAMS];
player_record *pr;
int team,i;
for(i=0;i<DLLMAX_TEAMS;i++)
TeamCount[i] = 0;
for(i=0;i<MAX_PLAYER_RECORDS;i++){
int slot = SortedPlayers[i];
pr = DMFCBase->GetPlayerRecord(slot);
if(pr->state!=STATE_EMPTY){
if(DMFCBase->IsPlayerDedicatedServer(pr))
continue;//skip dedicated server
team = (pr->state==STATE_INGAME)?dPlayers[pr->pnum].team:pr->team;
if(team>=NumOfTeams)
team = 0;
SortedPLRPlayers[team][TeamCount[team]] = slot;
TeamCount[team]++;
}
}
for(i=0;i<DLLMAX_TEAMS;i++){
if(TeamCount[i]<MAX_PLAYER_RECORDS)
SortedPLRPlayers[i][TeamCount[i]] = -1;
}
DMFCBase->OnPLRInit();
}
void OnPLRInterval(void)
{
DMFCBase->OnPLRInterval();
int TeamCol = 35;
int NameCol = 190;
int PointCol = 280;
int BlunderCol = 340;
int KillsCol = 410;
int DeathsCol = 450;
int SuicidesCol = 490;
int y = 40;
int slot;
player_record *pr;
tPlayerStat *stat;
DLLgrtext_SetFont((DMFCBase->GetGameFontTranslateArray())[SMALL_UI_FONT_INDEX]);
int height = DLLgrfont_GetHeight((DMFCBase->GetGameFontTranslateArray())[SMALL_UI_FONT_INDEX]) + 1;
//print out header
DLLgrtext_SetColor(GR_RGB(255,255,150));
DLLgrtext_Printf(NameCol,y,TXT_PILOT);
DLLgrtext_Printf(PointCol,y,TXT_POINTS);
DLLgrtext_Printf(BlunderCol,y,TXT_BLUNDERS);
DLLgrtext_Printf(KillsCol,y,TXT_KILLSSHORT);
DLLgrtext_Printf(DeathsCol,y,TXT_DEATHSSHORT);
DLLgrtext_Printf(SuicidesCol,y,TXT_SUICIDESSHORT);
y+=height;
bool has_members;
bool doing_connected = true;
do_disconnected_folk:
for(int team=0;team<NUM_TEAMS;team++){
//process this team
bool show_team_label;
show_team_label = false;
if(!doing_connected)
{
int temp_idx;
temp_idx = 0;
while(SortedPLRPlayers[team][temp_idx]!=-1)
{
int pnum=DMFCBase->WasPlayerInGameAtLevelEnd(SortedPLRPlayers[team][temp_idx]);
if(pnum==-1)
{
show_team_label = true;
break;
}
temp_idx++;
}
}else
{
show_team_label = true;
}
if(show_team_label){
//is there anyone on this team?
DLLgrtext_SetColor(DMFCBase->GetTeamColor(team));
DLLgrtext_Printf(TeamCol,y,TXT_HUDDISPLAY,DMFCBase->GetTeamString(team),TeamScores[team]);
}
has_members = false;
for(int index=0;index<MAX_PLAYER_RECORDS;index++){
//get the player num
slot = SortedPLRPlayers[team][index];
if(slot==-1)//we are done with this team
break;
pr = DMFCBase->GetPlayerRecord(slot);
if(pr && pr->state!=STATE_EMPTY){
if(DMFCBase->IsPlayerDedicatedServer(pr))
continue;//skip dedicated server
int pnum=DMFCBase->WasPlayerInGameAtLevelEnd(slot);
if( (doing_connected && pnum==-1) ||
(!doing_connected && pnum!=-1) )
continue;//we're not handling them right now
if(pnum!=-1)
{
DLLgrtext_SetColor(DMFCBase->GetTeamColor(team));
}else
{
DLLgrtext_SetColor(GR_RGB(128,128,128));
}
char tempbuffer[40];
strcpy(tempbuffer,pr->callsign);
DMFCBase->ClipString(PointCol - NameCol - 10,tempbuffer,true);
//valid player
stat = (tPlayerStat *)pr->user_info;
DLLgrtext_Printf(NameCol,y,"%s",tempbuffer);
DLLgrtext_Printf(PointCol,y,"%d",(stat)?stat->Score[DSTAT_LEVEL]:0);
DLLgrtext_Printf(BlunderCol,y,"%d",(stat)?stat->BadScore[DSTAT_LEVEL]:0);
DLLgrtext_Printf(KillsCol,y,"%d",pr->dstats.kills[DSTAT_LEVEL]);
DLLgrtext_Printf(DeathsCol,y,"%d",pr->dstats.deaths[DSTAT_LEVEL]);
DLLgrtext_Printf(SuicidesCol,y,"%d",pr->dstats.suicides[DSTAT_LEVEL]);
y+=height;
has_members = true;
if(y>=440)
goto quick_exit;
}
}//end for
//on to the next team
if(!has_members)
y+=height; //on to the next line
if(y>=440)
goto quick_exit;
}//end for
if(doing_connected)
{
doing_connected = false;
goto do_disconnected_folk;
}
quick_exit:;
}
void OnPrintScores(int level)
{
char buffer[256];
char name[50];
int t,i;
int pos[7];
int len[7];
for(i=0;i<NumOfTeams;i++)
{
sprintf(buffer,"%s:%d\n",DMFCBase->GetTeamString(i),TeamScores[i]);
DPrintf(buffer);
}
memset(buffer,' ',256);
pos[0] = 0; t = len[0] = 20; //give ample room for pilot name
pos[1] = pos[0] + t + 1; t = len[1] = strlen(TXT_POINTS);
pos[2] = pos[1] + t + 1; t = len[2] = strlen(TXT_BLUNDERS);
pos[3] = pos[1] + t + 1; t = len[3] = strlen(TXT_KILLS);
pos[4] = pos[2] + t + 1; t = len[4] = strlen(TXT_DEATHS);
pos[5] = pos[3] + t + 1; t = len[5] = strlen(TXT_SUICIDES);
pos[6] = pos[4] + t + 1; t = len[6] = strlen(TXT_PING);
memcpy(&buffer[pos[0]],TXT_PILOT,strlen(TXT_PILOT));
memcpy(&buffer[pos[1]],TXT_POINTS,len[1]);
memcpy(&buffer[pos[2]],TXT_BLUNDERS,len[2]);
memcpy(&buffer[pos[3]],TXT_KILLS,len[3]);
memcpy(&buffer[pos[4]],TXT_DEATHS,len[4]);
memcpy(&buffer[pos[5]],TXT_SUICIDES,len[5]);
memcpy(&buffer[pos[6]],TXT_PING,len[6]);
buffer[pos[6]+len[6]+1] = '\n';
buffer[pos[6]+len[6]+2] = '\0';
DPrintf(buffer);
int slot;
player_record *pr;
int pcount;
if(level<0 || level>=MAX_PLAYER_RECORDS)
pcount = MAX_PLAYER_RECORDS;
else
pcount = level;
int sortedplayers[MAX_PLAYER_RECORDS];
DMFCBase->GetSortedPlayerSlots(sortedplayers,MAX_PLAYER_RECORDS);
for(i=0;i<pcount;i++){
slot = sortedplayers[i];
pr = DMFCBase->GetPlayerRecord(slot);
tPlayerStat *stat;
if((pr)&&(pr->state!=STATE_EMPTY)){
if(DMFCBase->IsPlayerDedicatedServer(pr))
continue; //skip dedicated server
sprintf(name,"%s%s:",(pr->state==STATE_DISCONNECTED)?"*":"",pr->callsign);
name[19] = '\0';
stat = (tPlayerStat *)pr->user_info;
memset(buffer,' ',256);
t = strlen(name); memcpy(&buffer[pos[0]],name,(t<len[0])?t:len[0]);
sprintf(name,"%d",(stat)?stat->Score[DSTAT_LEVEL]:0);
t = strlen(name); memcpy(&buffer[pos[1]],name,(t<len[1])?t:len[1]);
sprintf(name,"%d",(stat)?stat->BadScore[DSTAT_LEVEL]:0);
t = strlen(name); memcpy(&buffer[pos[2]],name,(t<len[2])?t:len[2]);
sprintf(name,"%d",pr->dstats.kills[DSTAT_LEVEL]);
t = strlen(name); memcpy(&buffer[pos[3]],name,(t<len[2])?t:len[3]);
sprintf(name,"%d",pr->dstats.deaths[DSTAT_LEVEL]);
t = strlen(name); memcpy(&buffer[pos[4]],name,(t<len[3])?t:len[4]);
sprintf(name,"%d",pr->dstats.suicides[DSTAT_LEVEL]);
t = strlen(name); memcpy(&buffer[pos[5]],name,(t<len[4])?t:len[5]);
if(pr->state==STATE_INGAME)
sprintf(name,"%.0f",(DMFCBase->GetNetPlayers())[pr->pnum].ping_time*1000.0f);
else
strcpy(name,"---");
t = strlen(name); memcpy(&buffer[pos[6]],name,(t<len[6])?t:len[6]);
buffer[pos[6]+len[6]+1] = '\n';
buffer[pos[6]+len[6]+2] = '\0';
DPrintf(buffer);
}
}
}
void SaveStatsToFile(char *filename)
{
CFILE *file;
DLLOpenCFILE(&file,filename,"wt");
if(!file){
DLLmprintf((0,"Unable to open output file\n"));
return;
}
//write out game stats
#define BUFSIZE 150
char buffer[BUFSIZE];
char tempbuffer[25];
int sortedslots[MAX_PLAYER_RECORDS];
player_record *pr,*dpr;
tPInfoStat stat;
int count,length,p;
tPlayerStat *st;
//sort the stats
DMFCBase->GetSortedPlayerSlots(sortedslots,MAX_PLAYER_RECORDS);
SortTeams();
count = 1;
sprintf(buffer,TXT_SAVESTATSA,(DMFCBase->GetNetgameInfo())->name,(DMFCBase->GetCurrentMission())->cur_level);
DLLcf_WriteString(file,buffer);
for(p=0;p<NumOfTeams;p++){
int team_i = SortedTeams[p];
memset(buffer,' ',BUFSIZE);
sprintf(tempbuffer,TXT_SAVESTATSTEAM,DMFCBase->GetTeamString(team_i));
memcpy(&buffer[0],tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"[%d]",TeamScores[team_i]);
memcpy(&buffer[20],tempbuffer,strlen(tempbuffer));
buffer[20 + strlen(tempbuffer)] = '\0';
DLLcf_WriteString(file,buffer);
}
//Write team members
DLLcf_WriteString(file,""); //blank line
for(int t=0;t<NumOfTeams;t++){
int team_i = SortedTeams[t];
sprintf(buffer,TXT_SAVESTATSTEAM,DMFCBase->GetTeamString(team_i));
strcat(buffer,":");
DLLcf_WriteString(file,buffer);
for(p=0;p<MAX_PLAYER_RECORDS;p++){
pr = DMFCBase->GetPlayerRecord(sortedslots[p]);
if( pr && pr->state!=STATE_EMPTY) {
if(DMFCBase->IsPlayerDedicatedServer(pr))
continue;//skip dedicated server
if (pr->team == team_i) { //Check if current team
sprintf(buffer," %s",pr->callsign);
DLLcf_WriteString(file,buffer);
}
}
}
}
DLLcf_WriteString(file,""); //blank line
sprintf(buffer,TXT_SAVESTATSB);
DLLcf_WriteString(file,buffer);
sprintf(buffer,TXT_SAVESTATSC);
DLLcf_WriteString(file,buffer);
sprintf(buffer,"--------------------------------------------------------------------------------------");
DLLcf_WriteString(file,buffer);
for(p=0;p<MAX_PLAYER_RECORDS;p++){
pr = DMFCBase->GetPlayerRecord(sortedslots[p]);
if( pr && pr->state!=STATE_EMPTY) {
if(DMFCBase->IsPlayerDedicatedServer(pr))
continue;//skip dedicated server
st = (tPlayerStat *)pr->user_info;
memset(buffer,' ',BUFSIZE);
sprintf(tempbuffer,"%d)",count);
memcpy(&buffer[0],tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"%s%s",(pr->state==STATE_INGAME)?"":"*",pr->callsign);
memcpy(&buffer[5],tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"%d[%d]",(st)?st->Score[DSTAT_LEVEL]:0,(st)?st->Score[DSTAT_OVERALL]:0);
memcpy(&buffer[34],tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"%d[%d]",(st)?st->BadScore[DSTAT_LEVEL]:0,(st)?st->BadScore[DSTAT_OVERALL]:0);
memcpy(&buffer[42],tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"%d[%d]",pr->dstats.kills[DSTAT_LEVEL],pr->dstats.kills[DSTAT_OVERALL]);
memcpy(&buffer[55],tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"%d[%d]",pr->dstats.deaths[DSTAT_LEVEL],pr->dstats.deaths[DSTAT_OVERALL]);
memcpy(&buffer[67],tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"%d[%d]",pr->dstats.suicides[DSTAT_LEVEL],pr->dstats.suicides[DSTAT_OVERALL]);
memcpy(&buffer[78],tempbuffer,strlen(tempbuffer));
int pos;
pos = 78 + strlen(tempbuffer) + 1;
if(pos<BUFSIZE)
buffer[pos] = '\0';
buffer[BUFSIZE-1] = '\0';
DLLcf_WriteString(file,buffer);
count++;
}
}
DLLcf_WriteString(file,TXT_SAVESTATSD);
count =1;
for(p=0;p<MAX_PLAYER_RECORDS;p++){
pr = DMFCBase->GetPlayerRecord(p);
if( pr && pr->state!=STATE_EMPTY) {
if(DMFCBase->IsPlayerDedicatedServer(pr))
continue;//skip dedicated server
//Write out header
sprintf(buffer,"%d) %s%s (%s)",count,(pr->state==STATE_INGAME)?"":"*",pr->callsign,DMFCBase->GetTeamString(pr->team));
DLLcf_WriteString(file,buffer);
length = strlen(buffer);
memset(buffer,'=',length);
buffer[length] = '\0';
DLLcf_WriteString(file,buffer);
//time in game
sprintf(buffer,TXT_TIMEINGAME,DMFCBase->GetTimeString(DMFCBase->GetTimeInGame(p)));
DLLcf_WriteString(file,buffer);
if(DMFCBase->FindPInfoStatFirst(p,&stat)){
sprintf(buffer,TXT_SAVESTATSE);
DLLcf_WriteString(file,buffer);
if(stat.slot!=p){
memset(buffer,' ',BUFSIZE);
dpr = DMFCBase->GetPlayerRecord(stat.slot);
if(dpr)
{
int pos;
sprintf(tempbuffer,"%s",DMFCBase->GetTeamString(dpr->team));
memcpy(buffer,tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"%s",dpr->callsign);
memcpy(&buffer[6],tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"%d",stat.kills);
memcpy(&buffer[36],tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"%d",stat.deaths);
memcpy(&buffer[46],tempbuffer,strlen(tempbuffer));
pos = 46 + strlen(tempbuffer) + 1;
if(pos<BUFSIZE)
buffer[pos] = '\0';
buffer[BUFSIZE-1] = '\0';
DLLcf_WriteString(file,buffer);
}
}
while(DMFCBase->FindPInfoStatNext(&stat)){
if(stat.slot!=p){
int pos;
memset(buffer,' ',BUFSIZE);
dpr = DMFCBase->GetPlayerRecord(stat.slot);
if(dpr)
{
sprintf(tempbuffer,"%s",dpr->callsign);
memcpy(buffer,tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"%d",stat.kills);
memcpy(&buffer[30],tempbuffer,strlen(tempbuffer));
sprintf(tempbuffer,"%d",stat.deaths);
memcpy(&buffer[40],tempbuffer,strlen(tempbuffer));
pos = 40 + strlen(tempbuffer) + 1;
if(pos<BUFSIZE)
buffer[pos] = '\0';
buffer[BUFSIZE-1] = '\0';
DLLcf_WriteString(file,buffer);
}
}
}
}
DMFCBase->FindPInfoStatClose();
DLLcf_WriteString(file,""); //skip a line
count++;
}
}
//done writing stats
DLLcfclose(file);
DLLAddHUDMessage(TXT_STATSSAVED);
}
#define ROOTFILENAME "MonsterBall"
void OnSaveStatsToFile(void)
{
char filename[256];
DMFCBase->GenerateStatFilename(filename,ROOTFILENAME,false);
SaveStatsToFile(filename);
}
void OnLevelEndSaveStatsToFile(void)
{
char filename[256];
DMFCBase->GenerateStatFilename(filename,ROOTFILENAME,true);
SaveStatsToFile(filename);
}
void OnDisconnectSaveStatsToFile(void)
{
char filename[256];
DMFCBase->GenerateStatFilename(filename,ROOTFILENAME,false);
SaveStatsToFile(filename);
}
//////////////////////////////////////////////
// NON-DMFC Functions
//////////////////////////////////////////////
void DisplayHUDScores(struct tHUDItem *hitem)
{
if(display_my_welcome)
{
DisplayWelcomeMessage(DMFCBase->GetPlayerNum());
display_my_welcome = false;
}
if(!monsterball_info_set || !players_sorted || DisplayScoreScreen) //interval hasn't been called yet or we are display the stats
return;
int height = DLLgrfont_GetHeight((DMFCBase->GetGameFontTranslateArray())[HUD_FONT_INDEX]) + 3;
int y,x,team,p;
ubyte alpha = DMFCBase->ConvertHUDAlpha((ubyte)255);
if(!ValidateOwner(&p,NULL)){
p = -1;
}
y = (DMFCBase->GetGameWindowH()/2) - ((height*2)/2);
x = 510;
team = DMFCBase->GetMyTeam();
DLLRenderHUDText(DMFCBase->GetTeamColor(team),255,0,x,0,TXT_SAVESTATSTEAM,DMFCBase->GetTeamString(team));
int powerteam = -1;
if(p!=-1)
powerteam = DMFCBase->GetPlayerTeam(p);
int myteam = DMFCBase->GetMyTeam();
int team_count;
//determine coordinates to use here
//we'll use a virtual width of 85 pixels on a 640x480 screen
//so first determine the new width
int name_width = 100.0f * DMFCBase->GetHudAspectX();
int score_width = DLLgrtext_GetTextLineWidth("888");
int name_x = DMFCBase->GetGameWindowW() - name_width - score_width - 10;
int score_x = DMFCBase->GetGameWindowW() - score_width - 5;
int icon_size = height-3;
int icon_x = name_x - icon_size - 3;
DLLgrtext_SetAlpha(alpha);
char name[256];
for(int i=0;i<NumOfTeams;i++){
team = SortedTeams[i];
if( (WhoJustScored!=team) || (DisplayScoreBlink) )
{
if(team==myteam && Highlight_bmp>BAD_BITMAP_HANDLE){
//draw the highlite bar in the background
DLLrend_SetAlphaValue(alpha*0.50f);
DLLrend_SetZBufferState (0);
DLLrend_SetTextureType (TT_LINEAR);
DLLrend_SetLighting (LS_NONE);
DLLrend_SetAlphaType (AT_CONSTANT_TEXTURE);
DLLrend_DrawScaledBitmap(name_x-2,y-2,score_x+score_width+2,y+height-1,Highlight_bmp,0,0,1,1,1.0);
DLLrend_SetZBufferState (1);
}
team_count = 0;
for(int q=0;q<DLLMAX_PLAYERS;q++){
if(DMFCBase->CheckPlayerNum(q) && dPlayers[q].team==team && !DMFCBase->IsPlayerDedicatedServer(q))
team_count++;
}
DLLgrtext_SetColor(DMFCBase->GetTeamColor(team));
sprintf(name,TXT_HUDSCORE,team_count,DMFCBase->GetTeamString(team));
DMFCBase->ClipString(name_width,name,true);
DLLgrtext_Printf(name_x,y,name);
DLLgrtext_Printf(score_x,y,"[%d]",TeamScores[team]);
}
y+=height;
}
}
void GetLastHitInfo(ubyte *data)
{
int size = 0;
LastHitPnum = (sbyte)MultiGetByte(data,&size);
}
void SendLastHitInfo(void)
{
int size = 0;
ubyte data[MAX_GAME_DATA_SIZE];
DMFCBase->StartPacket(data,SPID_HITINFO,&size);
MultiAddByte(LastHitPnum,data,&size);
DMFCBase->SendPacket(data,size,SP_ALL);
}
void GetGameStartPacket(ubyte *data)
{
int i,count = 0;
//team scores
for(i=0;i<DLLMAX_TEAMS;i++)
{
TeamScores[i] = MultiGetInt(data,&count);
}
//who has the Monsterball
sbyte temp;
temp = (sbyte)MultiGetByte(data,&count);
if(temp==-1){
Monsterball_info.owner_handle = OBJECT_HANDLE_NONE;
}else{
if(DMFCBase->CheckPlayerNum(temp))
Monsterball_info.owner_handle = dObjects[dPlayers[temp].objnum].handle;
else
Monsterball_info.owner_handle = OBJECT_HANDLE_NONE;
}
monsterball_info_set = true;
temp = (sbyte)MultiGetByte(data,&count);
NumOfTeams = temp;
//we need to find the objnum of the Monsterball...its there somewhere
DLLmprintf((0,"Looking for Monsterball in level\n"));
int objnum = -1;
for(i=0;i<MAX_OBJECTS;i++){
if( (dObjects[i].type==OBJ_ROBOT) && (dObjects[i].id==Monsterball_info.id) ){
//here it is
objnum = i;
break;
}
}
DLLmprintf((0,"Monsterball %s\n",(objnum==-1)?"Not Found":"Found"));
if(objnum==-1){
FatalError("Couldn't Find Monsterball when it should be there");
}
Monsterball_info.objhandle = dObjects[objnum].handle;
}
void SendGameStartPacket(int pnum)
{
int i,count = 0;
ubyte data[MAX_GAME_DATA_SIZE];
DMFCBase->StartPacket(data,SPID_NEWPLAYER,&count);
//add the team scores
for(i=0;i<DLLMAX_TEAMS;i++)
{
MultiAddInt(TeamScores[i],data,&count);
}
sbyte temp;
int p;
//who has the Monsterball if anyone
if(ValidateOwner(&p,NULL)){
temp = p;
}else{
temp = -1;
}
MultiAddByte(temp,data,&count);
//number of teams
temp = NumOfTeams;
MultiAddByte(temp,data,&count);
//we're done
DLLmprintf((0,"Sending Game State to %s\n",dPlayers[pnum].callsign));
DMFCBase->SendPacket(data,count,pnum);
}
bool GetMonsterballInfo(int id)
{
if(id==-1)
return false;
Monsterball_info.id = id;
//find the room with the RF_SPECIAL1
for(int i=0;i<=DMFCBase->GetHighestRoomIndex();i++){
if(dRooms[i].flags&RF_SPECIAL1){
//here's the Monsterball!
Monsterball_info.room = i;
DLLComputeRoomCenter(&Monsterball_info.pos,&dRooms[i]);
return true;
}
}
return false;
}
#define compGT(a,b) (a < b)
// insert sort
void SortTeams(void)
{
int t;
int i, j;
//copy scores into scoreinfo array
for(i=0;i<DLLMAX_TEAMS;i++)
{
SortedTeams[i] = i;
}
for(i=1;i<=DLLMAX_TEAMS-1;i++)
{
t=SortedTeams[i];
// Shift elements down until
// insertion point found.
for(j=i-1;j>=0 && compGT(TeamScores[SortedTeams[j]],TeamScores[t]); j--)
{
SortedTeams[j+1] = SortedTeams[j];
}
// insert
SortedTeams[j+1] = t;
}
}
void DisplayWelcomeMessage(int player_num)
{
char name_buffer[64];
strcpy(name_buffer,(DMFCBase->GetPlayers())[player_num].callsign);
if(player_num==DMFCBase->GetPlayerNum())
{
int team = DMFCBase->GetMyTeam();
if(team==-1)
return;
DLLAddHUDMessage(TXT_WELCOME,name_buffer);
DLLAddColoredHUDMessage(DMFCBase->GetTeamColor(team),TXT_ONTEAM,DMFCBase->GetTeamString(team));
}
else
{
int team = dPlayers[player_num].team;
if(team==-1)
return;
DLLAddColoredHUDMessage(DMFCBase->GetTeamColor(team),TXT_JOINED,name_buffer,DMFCBase->GetTeamString(team));
}
}
void OnTimerScore(void)
{
DisplayScoreBlink = !DisplayScoreBlink;
}
void OnTimerScoreKill(void)
{
DisplayScoreBlink = true;
WhoJustScored = WhoJustScoredTimer = -1;
}
// -------------------------------------------------------------------------
void HandleMonsterballCollideWithObject(object *ball,object *player,vector *point,vector *normal)
{
static int sound = -1;
if(sound==-1)
sound = DLLFindSoundName(IGNORE_TABLE(MONSTERBALL_SND_HIT));
if(sound!=-1)
DLLPlay3dSound(sound,ball,MAX_GAME_VOLUME/3);
//bump_object(ball,&player->mtype.phys_info.rotvel,&player->mtype.phys_info.velocity,
// &player->pos, &player->orient, player->mtype.phys_info.mass, player->size,point,normal,1.0f,1.0f);
}
void HandleMonsterballCollideWithWeapon(object *ball,weapon_collide_info *winfo,vector *point,vector *normal)
{
static int sound = -1;
if(sound==-1)
sound = DLLFindSoundName(IGNORE_TABLE(MONSTERBALL_SND_HIT));
if(sound!=-1)
DLLPlay3dSound(sound,ball);
bump_object(ball,&winfo->rotvel,&winfo->velocity,&winfo->pos,&winfo->orient,winfo->mass,
winfo->size,point,normal,-0.3f,5.0f);
}
void bump_object(object *object0, vector *rotvel, vector *velocity, vector *pos, matrix *orient, float mass, float size,vector *collision_point,vector *collision_normal,float rot_scalar,float vel_scalar)
{
object *t = NULL;
object *other = NULL;
ASSERT(_finite(rotvel->x) != 0);
ASSERT(_finite(rotvel->y) != 0);
ASSERT(_finite(rotvel->z) != 0);
ASSERT(_finite(object0->mtype.phys_info.rotvel.x) != 0);
ASSERT(_finite(object0->mtype.phys_info.rotvel.y) != 0);
ASSERT(_finite(object0->mtype.phys_info.rotvel.z) != 0);
ASSERT(_finite(velocity->x) != 0);
ASSERT(_finite(velocity->y) != 0);
ASSERT(_finite(velocity->z) != 0);
ASSERT(_finite(object0->mtype.phys_info.velocity.x) != 0);
ASSERT(_finite(object0->mtype.phys_info.velocity.y) != 0);
ASSERT(_finite(object0->mtype.phys_info.velocity.z) != 0);
vector r1 = *collision_point - object0->pos;
vector r2 = *collision_point - (*pos);
vector w1;
vector w2;
vector n1;
vector n2;
float temp1;
float temp2;
float j;
matrix o_t1 = object0->orient;
matrix o_t2 = *orient;
DLLvm_TransposeMatrix(&o_t1);
DLLvm_TransposeMatrix(&o_t2);
vector cmp1 = object0->mtype.phys_info.rotvel * o_t1;
vector cmp2 = (*rotvel) * o_t2;
DLLConvertEulerToAxisAmount(&cmp1, &n1, &temp1);
DLLConvertEulerToAxisAmount(&cmp2, &n2, &temp2);
n1 *= temp1;
n2 *= temp2;
if(temp1 != 0.0f){
DLLvm_CrossProduct(&w1, &n1, &r1);
}else{
w1.x = 0;
w1.y = 0;
w1.z = 0;
}
if(temp2 != 0.0f){
DLLvm_CrossProduct(&w2, &n2, &r2);
}else{
w2.x = 0;
w2.y = 0;
w2.z = 0;
}
vector p1 = object0->mtype.phys_info.velocity + w1;
vector p2 = (*velocity) + w2;
float v_rel;
float m1 = object0->mtype.phys_info.mass;
float m2 = mass;
ASSERT(m1 != 0.0f && m2 != 0.0f);
v_rel = *collision_normal * (p1 - p2);
float e;
e = vel_scalar;
vector c1;
vector c2;
vector cc1;
vector cc2;
float cv1;
float cv2;
float i1 = (2.0f/5.0f)*m1*object0->size*object0->size;
float i2 = (2.0f/5.0f)*m2*size;
if(i1 < .0000001)
i1 = .0000001f;
if(i2 < .0000001)
i2 = .0000001f;
DLLvm_CrossProduct(&c1, &r1, collision_normal);
DLLvm_CrossProduct(&c2, &r2, collision_normal);
c1 = c1/i1;
c2 = c2/i2;
DLLvm_CrossProduct(&cc1, &c1, &r1);
DLLvm_CrossProduct(&cc2, &c2, &r2);
cv1 = (*collision_normal)*c1;
cv2 = (*collision_normal)*c2;
j = (-(1.0f + e))*v_rel;
j /= (1/m1 + 1/m2 + cv1 + cv2);
//apply the force to the player
vector new_vel;
new_vel = ((j*(*collision_normal))/m1);
//make sure the velocity falls within a valid range
float mag = DLLvm_GetMagnitudeFast(&new_vel);
new_vel/=mag;
mag=min(mag,20);
mag=max(mag,10);
new_vel*=mag;
object0->mtype.phys_info.velocity += new_vel;
vector jcn = j * (*collision_normal);
DLLvm_CrossProduct(&c1, &r1, &jcn);
n1 = (c1)/i1;
temp1 = DLLvm_NormalizeVector(&n1);
vector txx1;
DLLConvertAxisAmountToEuler(&n1, &temp1, &txx1);
float rotscale1;
rotscale1 = rot_scalar;
//change the player's rotational velocity
object0->mtype.phys_info.rotvel += (txx1*object0->orient) * rotscale1;
ASSERT(_finite(object0->mtype.phys_info.rotvel.x) != 0);
ASSERT(_finite(object0->mtype.phys_info.rotvel.y) != 0);
ASSERT(_finite(object0->mtype.phys_info.rotvel.z) != 0);
ASSERT(_finite(object0->mtype.phys_info.velocity.x) != 0);
ASSERT(_finite(object0->mtype.phys_info.velocity.y) != 0);
ASSERT(_finite(object0->mtype.phys_info.velocity.z) != 0);
}
bool ValidateOwner(int *pnum,object **obj)
{
if(Monsterball_info.owner_handle==OBJECT_HANDLE_NONE)
return false;
object *owner;
if(!DLLObjGet(Monsterball_info.owner_handle,&owner)){
//the object no longer exists
Monsterball_info.owner_handle = OBJECT_HANDLE_NONE;
return false;
}
if(owner->type!=OBJ_PLAYER || (dPlayers[owner->id].flags&(PLAYER_FLAGS_DYING|PLAYER_FLAGS_DEAD)) ){
//the owner isn't a player playing
Monsterball_info.owner_handle = OBJECT_HANDLE_NONE;
return false;
}
if(pnum)
*pnum = owner->id;
if(obj)
*obj = owner;
return true;
}
void DoMonsterballScoreEffect(void)
{
object *Monsterball;
if(!DLLObjGet(Monsterball_info.objhandle,&Monsterball))
return;
vector start_pos,end_pos;
int start_room,end_room;
bool doing_start = false;
vector m_pos,i_pos;
int m_room,i_room;
m_pos = Monsterball->pos;
m_room = Monsterball->roomnum;
i_pos = Monsterball_info.pos;
i_room = Monsterball_info.room;
//////////////////////////////////////
float lifetime = 4.0f;
float thickness = 17.5f;
float slidetime = 0.3f;
ushort color = GR_RGB16(30,255,30);
int numtiles = 1;
bool autotile = true;
int sat_count = 2;
int texture_id = DLLFindTextureName("ThickLineLightning");
//////////////////////////////////////
for(int q=0;q<2;q++)
{
////////////////////////////////
vector check_pos;
fvi_query fq;
fvi_info hit_data;
int hit_type;
fq.p0 = (doing_start)?&m_pos:&i_pos;
fq.p1 = &check_pos;
fq.startroom = (doing_start)?m_room:i_room;
fq.rad = 3.0f;
fq.flags = FQ_CHECK_OBJS|FQ_IGNORE_NON_LIGHTMAP_OBJECTS;
fq.thisobjnum = Monsterball - dObjects;
fq.ignore_obj_list = NULL;
//find top of room
check_pos = (doing_start)?m_pos:i_pos;
check_pos.y *= 50000;
hit_type = DLLfvi_FindIntersection(&fq,&hit_data);
if(hit_type==HIT_NONE)
return;
start_room = hit_data.hit_room;
start_pos = hit_data.hit_pnt;
check_pos = (doing_start)?m_pos:i_pos;
check_pos.y *= -50000;
hit_type = DLLfvi_FindIntersection(&fq,&hit_data);
if(hit_type==HIT_NONE)
return;
end_room = hit_data.hit_room;
end_pos = hit_data.hit_pnt;
int visnum=DLLVisEffectCreate (VIS_FIREBALL,THICK_LIGHTNING_INDEX,start_room,&start_pos);
if (visnum>=0)
{
vis_effect *vis=&dVisEffects[visnum];
vis->lifeleft=lifetime;
vis->lifetime=lifetime;
vis->end_pos=end_pos;
vis->custom_handle=texture_id;
vis->lighting_color=color;
vis->billboard_info.width=thickness;
vis->billboard_info.texture=autotile;
vis->velocity.x=sat_count;
vis->velocity.y=slidetime;
vis->velocity.z=numtiles;
vis->flags=VF_USES_LIFELEFT|VF_WINDSHIELD_EFFECT|VF_LINK_TO_VIEWER|VF_EXPAND;
vis->size=DLLvm_VectorDistanceQuick(&vis->pos,&vis->end_pos);
}
color=GR_RGB16(100,255,150);
visnum=DLLVisEffectCreate (VIS_FIREBALL,SUN_CORONA_INDEX,start_room,&start_pos);
if (visnum>=0)
{
vis_effect *vis=&dVisEffects[visnum];
vis->lifeleft=lifetime;
vis->lifetime=lifetime;
vis->lighting_color=color;
vis->flags=VF_USES_LIFELEFT|VF_WINDSHIELD_EFFECT|VF_LINK_TO_VIEWER;
vis->size=4;
}
visnum=DLLVisEffectCreate (VIS_FIREBALL,SUN_CORONA_INDEX,end_room,&end_pos);
if (visnum>=0)
{
vis_effect *vis=&dVisEffects[visnum];
vis->lifeleft=lifetime;
vis->lifetime=lifetime;
vis->lighting_color=color;
vis->flags=VF_USES_LIFELEFT|VF_WINDSHIELD_EFFECT|VF_LINK_TO_VIEWER;
vis->size=4;
}
DLLCreateRandomSparks(rand()%200 + 50,(doing_start)?&m_pos:&i_pos,(doing_start)?m_room:i_room,HOT_SPARK_INDEX,(rand()%3)+2);
doing_start = !doing_start;
}
if(Monsterball->effect_info)
{
Monsterball->effect_info->type_flags&=~EF_CLOAKED;
Monsterball->effect_info->type_flags|=EF_FADING_IN;
Monsterball->effect_info->fade_time=4.0f;
Monsterball->effect_info->cloak_time = 0;
Monsterball->effect_info->fade_max_time = 4.0f;
}
}
#ifdef MACINTOSH
#pragma export off
#endif