/* * Descent 3 * Copyright (C) 2024 Parallax Software * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "windows.h" #include "gamedll_header.h" #include // ########################Start DMFC Specifics########################## #include "DMFC.h" #include "powerball.h" // #include "tanarchystr.h" // ##########################End DMFC Specifics########################### ////////////////////////////////// // defines #define SPID_NEWPLAYER 0 #define SPID_COLLIDE 1 #define POWERBALL_ID_NAME "Powerball" #define POWERBALL_SND_SCORE "Extra life" #define POWERBALL_SND_PICKUP "" /* $$TABLE_GENERIC "Powerball" $$TABLE_SOUND "Extra life" */ ////////////////////////////////// // Structs struct tPlayerStat { int Score[2]; }; ////////////////////////////////// // Globals int SortedPLRPlayers[DLLMAX_TEAMS][DLLMAX_PLAYERS]; int TeamScores[DLLMAX_TEAMS]; bool DisplayScoreScreen = false; int NumOfTeams = 2; int SortedTeams[DLLMAX_TEAMS]; int SortedPlayers[DLLMAX_PLAYERS]; bool players_sorted = false; int PowerBallID = -1; int PowerBallRoom = -1; int PowerBallObjnum = -1; vector PowerBallPos; bool DisplayPowerBBlink = true; bool DisplayScoreBlink = true; int WhoJustScored = -1, WhoJustScoredTimer = -1; int WhoJustPowerB = -1, WhoJustPowerBTimer = -1; int ShieldRegenTimer = -1; int WhoHasPowerBall = -1; int RegenTimer = -1; int GoalRooms[DLLMAX_TEAMS]; int PowerBallIcon = -1; int PBallHazehandle = -1; CDmfcStats stat; ////////////////////////////////// // Prototypes void DisplayHUDScores(struct tHUDItem *hitem); void DisplayStats(void); void GetGameStartPacket(uint8_t *data); void SendGameStartPacket(int pnum); bool GetPowerBallInfo(int id); void UpdatePowerBallEffectOnPlayer(int pnum, bool pickedup); 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 SaveEndOfLevelStats(void); void SaveStatsToFile(char *filename); void HandlePlayerCollideWithPowerball(object *pobj, object *pball); void HandlePlayerLosingPowerball(object *pball); void HandleWeaponCollide(object *me_obj, object *it_obj); void GetCollideInfo(uint8_t *data); void SendCollideInfo(object *pobj, object *wobj, int towho, vector *point, vector *normal); void DetermineScore(int precord_num, int column_num, char *buffer, int buffer_size) { player_record *pr = PBall.GetPlayerRecord(precord_num); if (!pr || pr->state == STATE_EMPTY) { buffer[0] = '\0'; return; } tPlayerStat *stat = (tPlayerStat *)pr->user_info; sprintf(buffer, "%d[%d]", (stat) ? stat->Score[DSTAT_LEVEL] : 0, (stat) ? stat->Score[DSTAT_OVERALL] : 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, uint8_t alpha_to_use) { player_record *pr = PBall.GetPlayerRecord(precord_num); if (pr->state == STATE_INGAME && WhoHasPowerBall == pr->pnum) { DLLRenderHUDQuad(x + 2, y, 10, 10, 0, 0, 1, 1, PowerBallIcon, alpha_to_use); } } // 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; options->max_teams = 2; strcpy(options->game_name, "PowerBall"); } // Initializes the game function pointers void DLLFUNCCALL DLLGameInit(int *api_func, uint8_t *all_ok) { *all_ok = 1; PBall.LoadFunctions(api_func); PBall.GameInit(NumOfTeams); // DLLCreateStringTable("tanarchy.str",&StringTable,&StringTableSize); // DLLmprintf(0,"%d strings loaded from string table\n",StringTableSize); // add the death and suicide messages PBall.AddDeathMessage("%s was killed by %s", true); PBall.AddSuicideMessage("%s is suicidal"); // setup the Playerstats struct so DMFC can handle it automatically when a new player enters the game PBall.SetupPlayerRecord(sizeof(tPlayerStat)); // register special packet receivers PBall.RegisterPacketReceiver(SPID_NEWPLAYER, GetGameStartPacket); PBall.RegisterPacketReceiver(SPID_COLLIDE, GetCollideInfo); PBall.SetNumberOfTeams(NumOfTeams); PBall.AddHUDItemCallback(HI_TEXT, DisplayHUDScores); PowerBallIcon = DLLbm_AllocLoadFileBitmap("PowerBallIcon.ogf", 0); if (PowerBallIcon == -1) PowerBallIcon = BAD_BITMAP_HANDLE; DisplayScoreScreen = false; // TableFileAdd("PowerBall.Gam"); // Initialize the Stats Manager // ---------------------------- tDmfcStatsInit tsi; tDmfcStatsColumnInfo pl_col[7]; char gname[20]; strcpy(gname, "Powerball"); tsi.flags = DSIF_SHOW_PIC | DSIF_SHOW_OBSERVERICON | DSIF_SEPERATE_BY_TEAM; tsi.cColumnCountDetailed = 0; tsi.cColumnCountPlayerList = 7; 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, "Pilot"); pl_col[1].type = DSCOL_PILOT_NAME; pl_col[1].width = 120; pl_col[2].color_type = DSCOLOR_TEAM; strcpy(pl_col[2].title, "Score"); pl_col[2].type = DSCOL_CUSTOM; pl_col[2].width = 47; pl_col[3].color_type = DSCOLOR_TEAM; strcpy(pl_col[3].title, "Kills"); pl_col[3].type = DSCOL_KILLS_BOTH; pl_col[3].width = 47; pl_col[4].color_type = DSCOLOR_TEAM; strcpy(pl_col[4].title, "Deaths"); pl_col[4].type = DSCOL_DEATHS_BOTH; pl_col[4].width = 57; pl_col[5].color_type = DSCOLOR_TEAM; strcpy(pl_col[5].title, "Suicides"); pl_col[5].type = DSCOL_SUICIDES_BOTH; pl_col[5].width = 65; pl_col[6].color_type = DSCOLOR_TEAM; strcpy(pl_col[6].title, "Ping"); pl_col[6].type = DSCOL_PING; pl_col[6].width = 40; stat.Initialize(&tsi); PBallHazehandle = DLLbm_AllocBitmap(32, 32, 0); if (PBallHazehandle > BAD_BITMAP_HANDLE) { uint16_t *data = DLLbm_data(PBallHazehandle, 0); for (int i = 0; i < 32 * 32; i++) { data[i] = GR_RGB16(80, 200, 255) | OPAQUE_FLAG; } } else { Int3(); } } // Called when the DLL is shutdown void DLLFUNCCALL DLLGameClose() { if (PowerBallIcon > BAD_BITMAP_HANDLE) DLLbm_FreeBitmap(PowerBallIcon); if (PBallHazehandle > BAD_BITMAP_HANDLE) DLLbm_FreeBitmap(PBallHazehandle); if (RegenTimer != -1) PBall.KillTimer(RegenTimer); PBall.GameClose(); // DLLDestroyStringTable(StringTable,StringTableSize); } void DMFCApp::OnClientPlayerDisconnect(int player_num) { if (player_num == WhoHasPowerBall) HandlePowerballLoss(player_num, true); DMFCBase::OnClientPlayerDisconnect(player_num); } void DMFCApp::OnPlayerEntersObserver(int pnum, object *piggy) { if (pnum == WhoHasPowerBall) HandlePowerballLoss(pnum, false); DMFCBase::OnPlayerEntersObserver(pnum, piggy); } bool DMFCApp::OnCanChangeTeam(int pnum, int newteam) { if (!DMFCBase::OnCanChangeTeam(pnum, newteam)) return false; if (WhoHasPowerBall == pnum) { AnnounceTeamChangeDeny(pnum); return false; } return true; } void DMFCApp::OnHUDInterval(void) { stat.DoFrame(); DisplayOutrageLogo(); DMFCBase::OnHUDInterval(); } void DMFCApp::OnInterval(void) { GetSortedPlayerSlots(SortedPlayers, DLLMAX_PLAYERS); players_sorted = true; SortTeams(); DMFCBase::OnInterval(); } void DMFCApp::OnKeypress(int key) { switch (key) { case K_F7: DisplayScoreScreen = !DisplayScoreScreen; PBall.EnableOnScreenMenu(false); stat.Enable(DisplayScoreScreen); break; case K_PAGEDOWN: if (DisplayScoreScreen) { stat.ScrollDown(); Data->iRet = 1; } break; case K_PAGEUP: if (DisplayScoreScreen) { stat.ScrollUp(); Data->iRet = 1; } break; case K_F6: DisplayScoreScreen = false; stat.Enable(false); break; } DMFCBase::OnKeypress(key); } // The server has just started, so clear out all the stats and game info void DMFCApp::OnServerGameCreated(void) { DMFCBase::OnServerGameCreated(); player_record *pr; tPlayerStat *stat; for (int i = 0; i < MAX_PLAYER_RECORDS; i++) { pr = GetPlayerRecord(i); stat = (tPlayerStat *)pr->user_info; if (stat) { stat->Score[DSTAT_LEVEL] = stat->Score[DSTAT_OVERALL] = 0; } } for (i = 0; i < NumOfTeams; i++) { TeamScores[i] = 0; } } void DMFCApp::OnServerCollide(object *me_obj, object *it_obj) { if (!me_obj || !it_obj) return; if (it_obj->type == OBJ_PLAYER && me_obj->type == OBJ_POWERUP && me_obj->id == PowerBallID && -1 == WhoHasPowerBall) { CallClientEvent(EVT_CLIENT_GAMECOLLIDE, GetMeObjNum(), GetItObjNum(), -1); OnClientCollide(me_obj, it_obj); } DMFCBase::OnServerCollide(me_obj, it_obj); } void DMFCApp::OnClientCollide(object *me_obj, object *it_obj) { static int sound = -1; WhoHasPowerBall = it_obj->id; if (WhoHasPowerBall == GetPlayerNum()) { // we need to turn off movement Players[GetPlayerNum()].movement_scalar = 0; } if (sound == -1) sound = DLLFindSoundName(IGNORE_TABLE(POWERBALL_SND_SCORE)); if (sound != -1) DLLPlay3dSound(sound, &Objects[Players[GetPlayerNum()].objnum]); // Set a Timer to display if (WhoJustPowerBTimer != -1) KillTimer(WhoJustPowerBTimer); WhoJustPowerBTimer = SetTimerInterval(OnTimer, 0.5f, 5.0f, OnTimerKill); WhoJustPowerB = GetPlayerTeam(it_obj->id); DLLAddHUDMessage("%s Has Picked Up The PowerBall!", Players[it_obj->id].callsign); HandlePlayerCollideWithPowerball(it_obj, &Objects[PowerBallObjnum]); DMFCBase::OnClientCollide(me_obj, it_obj); } void DMFCApp::OnServerPlayerChangeSegment(int player_num, int newseg, int oldseg) { if (player_num == -1) return; if ((WhoHasPowerBall == player_num) && (newseg == GoalRooms[GetPlayerTeam(player_num)])) { CallClientEvent(EVT_CLIENT_GAMEPLAYERCHANGESEG, GetMeObjNum(), GetItObjNum(), -1); OnClientPlayerChangeSegment(player_num, newseg, oldseg); } } void DMFCApp::OnClientPlayerChangeSegment(int player_num, int newseg, int oldseg) { static int sound = -1; // if we got here than the player with the powerball has entered his team's goal! if (IsPlayerDedicatedServer(player_num)) return; // dedicated server HandlePowerballLoss(player_num, false); DLLObjSetPos(&Objects[PowerBallObjnum], &PowerBallPos, PowerBallRoom, NULL, false); WhoHasPowerBall = -1; DLLAddHUDMessage("%s Team Scores!!", GetTeamString(Players[player_num].team)); tPlayerStat *stat = (tPlayerStat *)GetPlayerRecordData(player_num); if (stat) { stat->Score[DSTAT_LEVEL]++; stat->Score[DSTAT_OVERALL]++; } TeamScores[Players[player_num].team]++; if (sound == -1) sound = DLLFindSoundName(IGNORE_TABLE(POWERBALL_SND_SCORE)); if (sound != -1) DLLPlay3dSound(sound, &Objects[Players[GetPlayerNum()].objnum]); // do killgoal check int goal; if ((GetScoreLimit(&goal)) && (GetLocalRole() == LR_SERVER)) { if (TeamScores[Players[player_num].team] >= goal) EndLevel(); } // Set a Timer to display if (WhoJustScoredTimer != -1) KillTimer(WhoJustScoredTimer); WhoJustScoredTimer = SetTimerInterval(OnTimerScore, 0.5f, 5.0f, OnTimerScoreKill); WhoJustScored = Players[player_num].team; } // The server has started a new level, so clear out any scores needed to be reset void DMFCApp::OnClientLevelStart(void) { DMFCBase::OnClientLevelStart(); player_record *pr; tPlayerStat *stat; for (int i = 0; i < MAX_PLAYER_RECORDS; i++) { pr = GetPlayerRecord(i); if (pr) stat = (tPlayerStat *)pr->user_info; else stat = NULL; if (stat) stat->Score[DSTAT_LEVEL] = 0; } for (i = 0; i < NumOfTeams; i++) { TeamScores[i] = 0; GoalRooms[i] = DLLGetGoalRoomForTeam(i); } DLLMultiPaintGoalRooms(); DLLmprintf(0, "Getting powerball info\n"); if (!GetPowerBallInfo(DLLFindObjectIDName(IGNORE_TABLE(POWERBALL_ID_NAME)))) { FatalError("Error finding Powerball room\n"); } if (GetLocalRole() == LR_SERVER) { PowerBallObjnum = DLLObjCreate(OBJ_POWERUP, PowerBallID, PowerBallRoom, &PowerBallPos, NULL); if (PowerBallObjnum == -1) FatalError("Unable to create PowerBall"); DLLMultiSendObject(&Objects[PowerBallObjnum], 1); if (RegenTimer != -1) KillTimer(RegenTimer); RegenTimer = SetTimerInterval(OnTimerRegen, 0.1f, 3600.0f, OnTimerRegenKill); } else { RequestGameState(); } players_sorted = false; } void DMFCApp::OnClientLevelEnd(void) { SaveEndOfLevelStats(); DMFCBase::OnClientLevelEnd(); } // A New Player has entered the game, so we want to send him a game status packet that // has information about the game void DMFCApp::OnGameStateRequest(int player_num) { SendGameStartPacket(player_num); DMFCBase::OnGameStateRequest(player_num); } // A new player has entered the game, zero their stats out void DMFCApp::OnPlayerConnect(int player_num) { tPlayerStat *stat = (tPlayerStat *)GetPlayerRecordData(player_num); if (stat) { stat->Score[DSTAT_LEVEL] = 0; stat->Score[DSTAT_OVERALL] = 0; } DMFCBase::OnPlayerConnect(player_num); DisplayWelcomeMessage(player_num); } // We need to adjust the scores void DMFCApp::OnClientPlayerKilled(object *killer_obj, int victim_pnum) { int kpnum; if (killer_obj) { if ((killer_obj->type == OBJ_PLAYER) || (killer_obj->type == OBJ_GHOST)) kpnum = killer_obj->id; else if (killer_obj->type == OBJ_ROBOT || (killer_obj->type == OBJ_BUILDING && killer_obj->ai_info)) { // countermeasure kill kpnum = GetCounterMeasureOwner(killer_obj); } else { kpnum = -1; } } else kpnum = -1; HandlePowerballLoss(victim_pnum, (bool)(victim_pnum != kpnum && kpnum != -1)); DMFCBase::OnClientPlayerKilled(killer_obj, victim_pnum); } void DMFCApp::HandlePowerballLoss(int pnum, bool spew_shields) { static int shield_id = -1; if (WhoHasPowerBall == -1) return; if (pnum == WhoHasPowerBall) { DLLAddHUDMessage("%s Lost The PowerBall!", Players[pnum].callsign); if (WhoHasPowerBall == GetPlayerNum()) { // remove movement restriction Players[GetPlayerNum()].movement_scalar = 1; } } HandlePlayerLosingPowerball(&Objects[PowerBallObjnum]); if (GetLocalRole() == LR_SERVER && spew_shields) { // add a bunch of shield powerups into the inventory so they spew out if (shield_id == -1) { shield_id = DLLFindObjectIDName("Shield"); } if (shield_id != -1) { int max = rand() % 4 + 1; for (int i = 0; i < max; i++) DLLInvAddTypeID(pnum, OBJ_POWERUP, shield_id); } } WhoHasPowerBall = -1; UpdatePowerBallEffectOnPlayer(pnum, false); } bool compare_slots(int a, int b) { int ascore, bscore; player_record *apr, *bpr; tPlayerStat *astat, *bstat; apr = PBall.GetPlayerRecord(a); bpr = PBall.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 DMFCApp::OnPLRInit(void) { 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(SortedPlayers, tempsort, DLLMAX_PLAYERS * sizeof(int)); // Now fill in the final structure of sorted names int TeamCount[DLLMAX_TEAMS]; player_record *pr; int team; for (i = 0; i < NumOfTeams; i++) TeamCount[i] = 0; for (i = 0; i < DLLMAX_PLAYERS; i++) { int slot = SortedPlayers[i]; pr = GetPlayerRecord(slot); if (pr->state != STATE_EMPTY) { if (IsPlayerDedicatedServer(pr)) continue; // skip dedicated server team = (pr->state == STATE_INGAME) ? Players[pr->pnum].team : pr->team; if (team >= NumOfTeams) team = 0; SortedPLRPlayers[team][TeamCount[team]] = slot; TeamCount[team]++; } } for (i = 0; i < NumOfTeams; i++) { if (TeamCount[i] < DLLMAX_PLAYERS) SortedPLRPlayers[i][TeamCount[i]] = -1; } DMFCBase::OnPLRInit(); } void DMFCApp::OnPLRInterval(void) { DMFCBase::OnPLRInterval(); int TeamCol = 100; int NameCol = 210; int KillsCol = 350; int DeathsCol = 400; int SuicidesCol = 450; int y = 110; int slot; player_record *pr; tPlayerStat *stat; DLLgrtext_SetFont(Game_fonts[HUD_FONT_INDEX]); int height = DLLgrfont_GetHeight(Game_fonts[HUD_FONT_INDEX]) + 1; // print out header DLLgrtext_SetColor(GR_RGB(255, 40, 40)); DLLgrtext_Printf(NameCol, y, "Pilot"); DLLgrtext_Printf(KillsCol, y, "Kills"); DLLgrtext_Printf(DeathsCol, y, "Deaths"); DLLgrtext_Printf(SuicidesCol, y, "Suicides"); y += height; for (int team = 0; team < MAX_TEAMS; team++) { // process this team if (SortedPLRPlayers[team][0] != -1) { // is there anyone on this team? DLLgrtext_SetColor(GetTeamColor(team)); DLLgrtext_Printf(TeamCol, y, "%s Team: %d", GetTeamString(team), TeamScores[team]); } for (int index = 0; index < DLLMAX_PLAYERS; index++) { // get the player num slot = SortedPLRPlayers[team][index]; pr = GetPlayerRecord(slot); if (slot == -1) // we are done with this team break; if (pr && pr->state != STATE_EMPTY) { if (IsPlayerDedicatedServer(pr)) continue; // skip dedicated server // valid player stat = (tPlayerStat *)pr->user_info; DLLgrtext_Printf(NameCol, y, "%s %d[%d]:", pr->callsign, (stat) ? stat->Score[DSTAT_LEVEL] : 0, (stat) ? stat->Score[DSTAT_OVERALL] : 0); DLLgrtext_Printf(KillsCol, y, "%d[%d]", pr->dstats.kills[DSTAT_LEVEL], pr->dstats.kills[DSTAT_OVERALL]); DLLgrtext_Printf(DeathsCol, y, "%d[%d]", pr->dstats.deaths[DSTAT_LEVEL], pr->dstats.deaths[DSTAT_OVERALL]); DLLgrtext_Printf(SuicidesCol, y, "%d[%d]", pr->dstats.suicides[DSTAT_LEVEL], pr->dstats.suicides[DSTAT_OVERALL]); y += height; } } // end for // on to the next team } // end for } 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; tPlayerStat *st; int count, length, p; // sort the stats PBall.GetSortedPlayerSlots(sortedslots, MAX_PLAYER_RECORDS); SortTeams(); count = 1; sprintf(buffer, "PowerBall\nGame: %s\nLevel: %d\n", PBall.Netgame->name, PBall.Current_mission->cur_level); DLLcf_WriteString(file, buffer); for (p = 0; p < NumOfTeams; p++) { int team_i = SortedTeams[p]; memset(buffer, ' ', BUFSIZE); sprintf(tempbuffer, "%s Team", PBall.GetTeamString(team_i)); memcpy(&buffer[0], tempbuffer, strlen(tempbuffer)); sprintf(tempbuffer, "[%d]", TeamScores[team_i]); memcpy(&buffer[20], tempbuffer, strlen(tempbuffer)); DLLcf_WriteString(file, buffer); } sprintf(buffer, "Current Level Rankings\n"); DLLcf_WriteString(file, buffer); sprintf(buffer, "Rank Name Score Kills Deaths Suicides"); DLLcf_WriteString(file, buffer); sprintf(buffer, "-----------------------------------------------------------------------------"); DLLcf_WriteString(file, buffer); for (p = 0; p < MAX_PLAYER_RECORDS; p++) { pr = PBall.GetPlayerRecord(sortedslots[p]); if (pr && pr->state != STATE_EMPTY) { if (PBall.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]", pr->dstats.kills[DSTAT_LEVEL], pr->dstats.kills[DSTAT_OVERALL]); memcpy(&buffer[46], tempbuffer, strlen(tempbuffer)); sprintf(tempbuffer, "%d[%d]", pr->dstats.deaths[DSTAT_LEVEL], pr->dstats.deaths[DSTAT_OVERALL]); memcpy(&buffer[58], tempbuffer, strlen(tempbuffer)); sprintf(tempbuffer, "%d[%d]", pr->dstats.suicides[DSTAT_LEVEL], pr->dstats.suicides[DSTAT_OVERALL]); memcpy(&buffer[69], tempbuffer, strlen(tempbuffer)); int pos; pos = 69 + strlen(tempbuffer) + 1; if (pos < BUFSIZE) buffer[pos] = '\0'; buffer[BUFSIZE - 1] = '\0'; DLLcf_WriteString(file, buffer); count++; } } DLLcf_WriteString(file, "\nIndividual Stats\n"); count = 1; for (p = 0; p < MAX_PLAYER_RECORDS; p++) { pr = PBall.GetPlayerRecord(p); if (pr && pr->state != STATE_EMPTY) { if (PBall.IsPlayerDedicatedServer(pr)) continue; // skip dedicated server // Write out header sprintf(buffer, "%d) %s%s", count, (pr->state == STATE_INGAME) ? "" : "*", pr->callsign); DLLcf_WriteString(file, buffer); length = strlen(buffer); memset(buffer, '=', length); buffer[length] = '\0'; DLLcf_WriteString(file, buffer); // time in game sprintf(buffer, "Total Time In Game: %s", basethis->GetTimeString(basethis->GetTimeInGame(p))); DLLcf_WriteString(file, buffer); if (PBall.FindPInfoStatFirst(p, &stat)) { sprintf(buffer, "Callsign: Kills: Deaths:"); DLLcf_WriteString(file, buffer); if (stat.slot != p) { memset(buffer, ' ', BUFSIZE); dpr = PBall.GetPlayerRecord(stat.slot); int pos; 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); } while (PBall.FindPInfoStatNext(&stat)) { if (stat.slot != p) { int pos; memset(buffer, ' ', BUFSIZE); dpr = PBall.GetPlayerRecord(stat.slot); 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); } } } PBall.FindPInfoStatClose(); DLLcf_WriteString(file, ""); // skip a line count++; } } // done writing stats DLLcfclose(file); DLLAddHUDMessage("Stats saved to %s", filename); } #define ROOTFILENAME "PowerBall" int stat_filecounter = 0; void DMFCApp::OnSaveStatsToFile(void) { char filename[100]; sprintf(filename, "%s%d.stats", ROOTFILENAME, stat_filecounter); stat_filecounter++; SaveStatsToFile(filename); } void SaveEndOfLevelStats(void) { int level = PBall.Current_mission->cur_level; char *name = PBall.Netgame->name; char filename[256]; sprintf(filename, "%s_%s_%d.stats", ROOTFILENAME, name, level); SaveStatsToFile(filename); } ////////////////////////////////////////////// // NON-DMFC Functions ////////////////////////////////////////////// void DisplayHUDScores(struct tHUDItem *hitem) { if (!players_sorted) return; int height = DLLgrfont_GetHeight(PBall.Game_fonts[HUD_FONT_INDEX]) + 3; int y, x, team; if (WhoHasPowerBall == PBall.GetPlayerNum()) { if (PBallHazehandle > BAD_BITMAP_HANDLE) { // DLLRenderHUDQuad(0,0,640,480,0,0,1,1,PBallHazehandle,100); } else { Int3(); } int w, h; w = DLLbm_w(PowerBallIcon, 0); h = DLLbm_h(PowerBallIcon, 0); x = 530; y = 10; DLLRenderHUDQuad(x, y, w, h, 0, 0, 1, 1, PowerBallIcon, 255); } y = 180; x = 520; team = PBall.GetMyTeam(); DLLRenderHUDText(PBall.GetTeamColor(team), 255, 0, x, 0, "%s Team", PBall.GetTeamString(team)); int powerteam = -1; if (WhoHasPowerBall != -1) powerteam = PBall.GetPlayerTeam(WhoHasPowerBall); for (int i = 0; i < NumOfTeams; i++) { team = SortedTeams[i]; if (((WhoJustScored != team) && (WhoJustPowerB != team)) || (DisplayPowerBBlink) && (DisplayScoreBlink)) { if (powerteam == team) DLLRenderHUDQuad(x - 12, y, 10, 10, 0, 0, 1, 1, PowerBallIcon, 255); DLLRenderHUDText(PBall.GetTeamColor(team), 255, 0, x, y, "%s Team", PBall.GetTeamString(team)); DLLRenderHUDText(PBall.GetTeamColor(team), 255, 0, 615, y, "[%d]", TeamScores[team]); } y += height; } } void GetGameStartPacket(uint8_t *data) { int size = 0; // team scores memcpy(TeamScores, &data[size], sizeof(int) * DLLMAX_TEAMS); size += (sizeof(int) * DLLMAX_TEAMS); // who has the powerball int8_t temp; memcpy(&temp, &data[size], sizeof(char)); size += sizeof(char); WhoHasPowerBall = temp; memcpy(&temp, &data[size], sizeof(char)); size += sizeof(char); NumOfTeams = temp; // Now based on what we have from the server set up our info if (WhoHasPowerBall != -1) { UpdatePowerBallEffectOnPlayer(WhoHasPowerBall, true); } // we need to find the objnum of the PowerBall...it is there somewhere DLLmprintf(0, "Looking for powerball in level\n"); for (int i = 0; i < MAX_OBJECTS; i++) { if ((PBall.Objects[i].type == OBJ_POWERUP) && (PBall.Objects[i].id == PowerBallID)) { // here it is PowerBallObjnum = i; break; } } DLLmprintf(0, "Powerball %s\n", (PowerBallObjnum == -1) ? "Not Found" : "Found"); if (PowerBallObjnum == -1) { FatalError("Couldn't Find PowerBall when it should be there"); } } void SendGameStartPacket(int pnum) { int maxsize = sizeof(int) * DLLMAX_TEAMS + 2 * sizeof(char); int size = 0; uint8_t *data = PBall.StartPacket(maxsize, SPID_NEWPLAYER); memset(data, 0, maxsize); // add the team scores memcpy(&data[size], TeamScores, sizeof(int) * DLLMAX_TEAMS); size += (sizeof(int) * DLLMAX_TEAMS); int8_t temp; // who has the powerball if anyone temp = WhoHasPowerBall; memcpy(&data[size], &temp, sizeof(char)); size += sizeof(char); // number of teams temp = NumOfTeams; memcpy(&data[size], &temp, sizeof(char)); size += sizeof(char); // we're done DLLmprintf(0, "Sending Game State to %s\n", PBall.Players[pnum].callsign); PBall.SendPacket(maxsize, pnum); } void UpdatePowerBallEffectOnPlayer(int pnum, bool pickedup) { float red[3], green[3], blue[3]; red[0] = 1; red[1] = 0; red[2] = 0; green[0] = 0; green[1] = 1; green[2] = 0; blue[0] = 0; blue[1] = 0; blue[2] = 1; if (pickedup) { DLLPlayerSetRotatingBall(pnum, 3, 0, red, green, blue); } else { DLLPlayerSetRotatingBall(pnum, 0, 0, NULL, NULL, NULL); } } bool GetPowerBallInfo(int id) { if (id == -1) return false; PowerBallID = id; // find the room with the RF_SPECIAL1 for (int i = 0; i <= *PBall.Highest_room_index; i++) { if (PBall.Rooms[i].flags & RF_SPECIAL1) { // here's the powerball! PowerBallRoom = i; DLLComputeRoomCenter(&PowerBallPos, &PBall.Rooms[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) { if (player_num == PBall.GetPlayerNum()) { int team = PBall.GetMyTeam(); if (team == -1) return; DLLAddHUDMessage("Welcome To Powerball %s", PBall.Players[PBall.GetPlayerNum()].callsign); DLLAddColoredHUDMessage(PBall.GetTeamColor(team), "You're On The %s Team", PBall.GetTeamString(team)); } else { int team = PBall.Players[player_num].team; if (team == -1) return; DLLAddColoredHUDMessage(PBall.GetTeamColor(team), "%s Has Joined The %s Team", PBall.Players[player_num].callsign, PBall.GetTeamString(team)); } } void OnTimerScore(void) { DisplayScoreBlink = !DisplayScoreBlink; } void OnTimerScoreKill(void) { DisplayScoreBlink = true; WhoJustScored = WhoJustScoredTimer = -1; } void OnTimer(void) { DisplayPowerBBlink = !DisplayPowerBBlink; } void OnTimerKill(void) { DisplayPowerBBlink = true; WhoJustPowerB = WhoJustPowerBTimer = -1; } void OnTimerRegen(void) { if (PBall.GetLocalRole() != LR_SERVER) return; if (WhoHasPowerBall == -1) return; float amount = 0; amount = 100 - PBall.Objects[PBall.Players[WhoHasPowerBall].objnum].shields; if (amount > 1) amount = 1; if (amount < 0) return; PBall.ShieldDelta[WhoHasPowerBall] -= amount; PBall.Objects[PBall.Players[WhoHasPowerBall].objnum].shields += amount; } void OnTimerRegenKill(void) { RegenTimer = PBall.SetTimerInterval(OnTimerRegen, 0.1f, 3600.0f, OnTimerRegenKill); } void HandlePlayerCollideWithPowerball(object *pobj, object *pball) { if (PBall.GetLocalRole() != LR_SERVER) return; // Attach the flag int ret = DLLAttachObject(pobj, 0, pball, 0, true); if (!ret) { // couldn't attach the flag mprintf(0, "PBALL: COULDN'T ATTACH BALL TO PLAYER\n"); } } void HandlePlayerLosingPowerball(object *pball) { if (PBall.GetLocalRole() != LR_SERVER) return; DLLUnattachFromParent(pball); } void HandleWeaponCollide(object *me_obj, object *it_obj) { mprintf(0, "Weapon Collide\n"); } void bump_two_objects(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 DMFCApp::OnServerWeaponCollide(object *pobj,object *wobj,vector *point,vector *normal,bool is_electrical) { if(is_electrical){ DMFCBase::OnServerWeaponCollide(pobj,wobj,point,normal,is_electrical); return; } if(pobj->id == WhoHasPowerBall){ if(pobj->id!=GetPlayerNum()) SendCollideInfo(pobj,wobj,pobj->id,point,normal); else bump_two_objects(pobj,&wobj->mtype.phys_info.rotvel,&wobj->mtype.phys_info.velocity, &wobj->pos,&wobj->orient,wobj->mtype.phys_info.mass,wobj->size,point,normal,0.1f,10.0f); } DMFCBase::OnServerWeaponCollide(pobj,wobj,point,normal,is_electrical); } */ void SendCollideInfo(object *pobj, object *wobj, int towho, vector *point, vector *normal) { int maxsize = (sizeof(float) * 26) + (sizeof(uint16_t) * 1); int size = 0; uint8_t *data = PBall.StartPacket(maxsize, SPID_COLLIDE); memset(data, 0, maxsize); memcpy(&data[size], &wobj->mtype.phys_info.rotvel, 3 * sizeof(float)); size += (sizeof(float) * 3); memcpy(&data[size], &wobj->mtype.phys_info.velocity, 3 * sizeof(float)); size += (sizeof(float) * 3); memcpy(&data[size], &wobj->pos, 3 * sizeof(float)); size += (sizeof(float) * 3); memcpy(&data[size], &wobj->mtype.phys_info.mass, sizeof(float)); size += sizeof(float); memcpy(&data[size], &wobj->size, sizeof(float)); size += sizeof(float); memcpy(&data[size], &wobj->orient.fvec, sizeof(float) * 3); size += (sizeof(float) * 3); memcpy(&data[size], &wobj->orient.rvec, sizeof(float) * 3); size += (sizeof(float) * 3); memcpy(&data[size], &wobj->orient.uvec, sizeof(float) * 3); size += (sizeof(float) * 3); memcpy(&data[size], point, sizeof(float) * 3); size += (sizeof(float) * 3); memcpy(&data[size], normal, sizeof(float) * 3); size += (sizeof(float) * 3); uint16_t id = (pobj - PBall.Objects); memcpy(&data[size], &id, sizeof(uint16_t)); size += sizeof(uint16_t); // we're done PBall.SendPacket(maxsize, towho); } void GetCollideInfo(uint8_t *data) { int size = 0; vector rotvel, velocity, pos, point, normal; matrix orient; float mass, fsize; uint16_t id; int objnum; memcpy(&rotvel, &data[size], 3 * sizeof(float)); size += (sizeof(float) * 3); memcpy(&velocity, &data[size], 3 * sizeof(float)); size += (sizeof(float) * 3); memcpy(&pos, &data[size], 3 * sizeof(float)); size += (sizeof(float) * 3); memcpy(&mass, &data[size], sizeof(float)); size += sizeof(float); memcpy(&fsize, &data[size], sizeof(float)); size += sizeof(float); memcpy(&orient.fvec, &data[size], sizeof(float) * 3); size += (sizeof(float) * 3); memcpy(&orient.rvec, &data[size], sizeof(float) * 3); size += (sizeof(float) * 3); memcpy(&orient.uvec, &data[size], sizeof(float) * 3); size += (sizeof(float) * 3); memcpy(&point, &data[size], sizeof(float) * 3); size += (sizeof(float) * 3); memcpy(&normal, &data[size], sizeof(float) * 3); size += (sizeof(float) * 3); memcpy(&id, &data[size], sizeof(uint16_t)); size += sizeof(uint16_t); objnum = PBall.ConvertServerToLocalObjnum(id); ASSERT(objnum != -1); if (objnum != -1) { bump_two_objects(&PBall.Objects[objnum], &rotvel, &velocity, &pos, &orient, mass, fsize, &point, &normal, 0.1f, 10.0f); } } void bump_two_objects(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) { // vector force; //dv, object *t = NULL; object *other = NULL; ASSERT(std::isfinite(rotvel->x)); ASSERT(std::isfinite(rotvel->y)); ASSERT(std::isfinite(rotvel->z)); ASSERT(std::isfinite(object0->mtype.phys_info.rotvel.x)); ASSERT(std::isfinite(object0->mtype.phys_info.rotvel.y)); ASSERT(std::isfinite(object0->mtype.phys_info.rotvel.z)); ASSERT(std::isfinite(velocity->x)); ASSERT(std::isfinite(velocity->y)); ASSERT(std::isfinite(velocity->z)); ASSERT(std::isfinite(object0->mtype.phys_info.velocity.x)); ASSERT(std::isfinite(object0->mtype.phys_info.velocity.y)); ASSERT(std::isfinite(object0->mtype.phys_info.velocity.z)); 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; // matrix i1; // matrix i2; 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 object0->mtype.phys_info.velocity += ((j * (*collision_normal)) / m1); 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(std::isfinite(object0->mtype.phys_info.rotvel.x)); ASSERT(std::isfinite(object0->mtype.phys_info.rotvel.y)); ASSERT(std::isfinite(object0->mtype.phys_info.rotvel.z)); ASSERT(std::isfinite(object0->mtype.phys_info.velocity.x)); ASSERT(std::isfinite(object0->mtype.phys_info.velocity.y)); ASSERT(std::isfinite(object0->mtype.phys_info.velocity.z)); }