From eed7412a8eb06451f75abce0e990b2914b9c963a Mon Sep 17 00:00:00 2001 From: Mattia Basaglia Date: Thu, 5 Feb 2015 15:32:19 +0100 Subject: [PATCH] Minigame code and cfg --- binds-xonotic.cfg | 1 + defaultXonotic.cfg | 3 +- hud_luma.cfg | 40 ++ keybinds.txt | 1 + keybinds.txt.de | 1 + keybinds.txt.es | 1 + keybinds.txt.fr | 1 + keybinds.txt.hu | 1 + keybinds.txt.it | 1 + keybinds.txt.ru | 1 + qcsrc/client/command/cl_cmd.qc | 11 +- qcsrc/client/hud.qc | 31 +- qcsrc/client/hud.qh | 55 +- qcsrc/client/main.qc | 9 + qcsrc/client/progs.src | 3 + qcsrc/client/scoreboard.qc | 3 +- qcsrc/client/view.qc | 6 +- qcsrc/common/constants.qh | 1 + qcsrc/common/minigames/cl_minigames.qc | 401 +++++++++++ qcsrc/common/minigames/cl_minigames.qh | 119 ++++ qcsrc/common/minigames/cl_minigames_hud.qc | 699 +++++++++++++++++++ qcsrc/common/minigames/minigame/all.qh | 105 +++ qcsrc/common/minigames/minigame/nmm.qc | 762 +++++++++++++++++++++ qcsrc/common/minigames/minigame/ttt.qc | 688 +++++++++++++++++++ qcsrc/common/minigames/minigames.qc | 131 ++++ qcsrc/common/minigames/minigames.qh | 124 ++++ qcsrc/common/minigames/sv_minigames.qc | 430 ++++++++++++ qcsrc/common/minigames/sv_minigames.qh | 54 ++ qcsrc/common/notifications.qc | 8 + qcsrc/common/notifications.qh | 54 +- qcsrc/dpdefs/csprogsdefs.qh | 1 + qcsrc/server/autocvars.qh | 2 + qcsrc/server/cl_client.qc | 37 +- qcsrc/server/cl_impulse.qc | 6 + qcsrc/server/cl_player.qc | 11 + qcsrc/server/command/cmd.qc | 1 + qcsrc/server/g_world.qc | 2 + qcsrc/server/progs.src | 2 + 38 files changed, 3770 insertions(+), 37 deletions(-) create mode 100644 qcsrc/common/minigames/cl_minigames.qc create mode 100644 qcsrc/common/minigames/cl_minigames.qh create mode 100644 qcsrc/common/minigames/cl_minigames_hud.qc create mode 100644 qcsrc/common/minigames/minigame/all.qh create mode 100644 qcsrc/common/minigames/minigame/nmm.qc create mode 100644 qcsrc/common/minigames/minigame/ttt.qc create mode 100644 qcsrc/common/minigames/minigames.qc create mode 100644 qcsrc/common/minigames/minigames.qh create mode 100644 qcsrc/common/minigames/sv_minigames.qc create mode 100644 qcsrc/common/minigames/sv_minigames.qh diff --git a/binds-xonotic.cfg b/binds-xonotic.cfg index f48842f8c..cc40398e5 100644 --- a/binds-xonotic.cfg +++ b/binds-xonotic.cfg @@ -56,6 +56,7 @@ bind u "+con_chat_maximize" bind m +hud_panel_radar_maximized bind i +show_info bind PAUSE pause +bind F9 "cl_cmd hud minigame" bind F10 menu_showquitdialog bind F11 disconnect bind F12 screenshot diff --git a/defaultXonotic.cfg b/defaultXonotic.cfg index 034b3bb32..c8321edbc 100644 --- a/defaultXonotic.cfg +++ b/defaultXonotic.cfg @@ -1100,7 +1100,8 @@ seta cl_gentle_damage 0 "client side gentle mode (only replaces damage flash); set g_jetpack 0 "Jetpack mutator" set g_running_guns 0 "... or wonder, till it drives you mad, what would have followed if you had." -set g_bastet 0 "don't try" +set sv_minigames 1 "Allow minigames" +set sv_minigames_observer 1 "Force minigame players to be observers. 0: don't move them to observer, 1: move them to observer, 2: force observer" set _urllib_nextslot 0 "temp variable" set cl_warpzone_usetrace 1 "do not touch" diff --git a/hud_luma.cfg b/hud_luma.cfg index 4f801509e..ec4e415f2 100644 --- a/hud_luma.cfg +++ b/hud_luma.cfg @@ -309,4 +309,44 @@ seta hud_panel_buffs_bg_alpha "" seta hud_panel_buffs_bg_border "" seta hud_panel_buffs_bg_padding "" +seta hud_panel_minigameboard "1" +seta hud_panel_minigameboard_pos "0.22 0.15" +seta hud_panel_minigameboard_size "0.50 0.60" +seta hud_panel_minigameboard_bg "border_small" +seta hud_panel_minigameboard_bg_color "" +seta hud_panel_minigameboard_bg_color_team "" +seta hud_panel_minigameboard_bg_alpha "" +seta hud_panel_minigameboard_bg_border "" +seta hud_panel_minigameboard_bg_padding "" + +seta hud_panel_minigamestatus "1" +seta hud_panel_minigamestatus_pos "0.74 0.15" +seta hud_panel_minigamestatus_size "0.2 0.60" +seta hud_panel_minigamestatus_bg "border_small" +seta hud_panel_minigamestatus_bg_color "" +seta hud_panel_minigamestatus_bg_color_team "" +seta hud_panel_minigamestatus_bg_alpha "" +seta hud_panel_minigamestatus_bg_border "" +seta hud_panel_minigamestatus_bg_padding "" + +seta hud_panel_minigamehelp "1" +seta hud_panel_minigamehelp_pos "0.22 0.78" +seta hud_panel_minigamehelp_size "0.50 0.20" +seta hud_panel_minigamehelp_bg "" +seta hud_panel_minigamehelp_bg_color "" +seta hud_panel_minigamehelp_bg_color_team "" +seta hud_panel_minigamehelp_bg_alpha "" +seta hud_panel_minigamehelp_bg_border "" +seta hud_panel_minigamehelp_bg_padding "" + +seta hud_panel_minigamemenu "0" +seta hud_panel_minigamemenu_pos "0 0.26" +seta hud_panel_minigamemenu_size "0.2 0.49" +seta hud_panel_minigamemenu_bg "border_small" +seta hud_panel_minigamemenu_bg_color "" +seta hud_panel_minigamemenu_bg_color_team "" +seta hud_panel_minigamemenu_bg_alpha "" +seta hud_panel_minigamemenu_bg_border "" +seta hud_panel_minigamemenu_bg_padding "" + menu_sync diff --git a/keybinds.txt b/keybinds.txt index 189d02eb4..9cfbccaa9 100644 --- a/keybinds.txt +++ b/keybinds.txt @@ -35,6 +35,7 @@ "+showscores" "show scores" "screenshot" "screen shot" "+hud_panel_radar_maximized" "maximize radar" +"cl_cmd hud minigame" "toggle minigame menu" "" "" "" "Communicate" "messagemode" "public chat" diff --git a/keybinds.txt.de b/keybinds.txt.de index 0c2aaf2b1..0f30ce2bf 100644 --- a/keybinds.txt.de +++ b/keybinds.txt.de @@ -35,6 +35,7 @@ "+showscores" "Tabelle anzeigen" "screenshot" "Bildschirmfoto" "+hud_panel_radar_maximized" "Radar maximieren" +"cl_cmd hud minigame" "Minispiel-Menu an- und ausschalten" "" "" "" "Kommunikation" "messagemode" "Nachricht an alle" diff --git a/keybinds.txt.es b/keybinds.txt.es index 51d9bfc39..79c312493 100644 --- a/keybinds.txt.es +++ b/keybinds.txt.es @@ -35,6 +35,7 @@ "+showscores" "mostrar puntaje" "screenshot" "captura de pantalla" "+hud_panel_radar_maximized" "maximize radar (FIXME)" +"cl_cmd hud minigame" "toggle minigame menu (FIXME)" "" "" "" "Communicación" "messagemode" "chat público" diff --git a/keybinds.txt.fr b/keybinds.txt.fr index 15a21f01e..b5275b713 100644 --- a/keybinds.txt.fr +++ b/keybinds.txt.fr @@ -35,6 +35,7 @@ "+showscores" "afficher les scores" "screenshot" "capture d'écran" "+hud_panel_radar_maximized" "agrandir le radar" +"cl_cmd hud minigame" "toggle minigame menu (FIXME)" "" "" "" "Communication" "messagemode" "tchat public" diff --git a/keybinds.txt.hu b/keybinds.txt.hu index 3ae11f77f..e22299a44 100644 --- a/keybinds.txt.hu +++ b/keybinds.txt.hu @@ -35,6 +35,7 @@ "+showscores" "pontszámok" "screenshot" "kép mentés" "+hud_panel_radar_maximized" "maximize radar (FIXME)" +"cl_cmd hud minigame" "toggle minigame menu (FIXME)" "" "" "" "Kommunikáció" "messagemode" "nyilvános beszélgetés" diff --git a/keybinds.txt.it b/keybinds.txt.it index 40f921033..069f9dfbb 100644 --- a/keybinds.txt.it +++ b/keybinds.txt.it @@ -35,6 +35,7 @@ "+showscores" "mostra punteggi" "screenshot" "screenshot" "+hud_panel_radar_maximized" "massimizza radar" +"cl_cmd hud minigame" "attiva/disattiva il menù dei giochini" "" "" "" "Comunicazione" "messagemode" "chat pubblica" diff --git a/keybinds.txt.ru b/keybinds.txt.ru index 7ab93ff8f..7be181d02 100644 --- a/keybinds.txt.ru +++ b/keybinds.txt.ru @@ -35,6 +35,7 @@ "+showscores" "показать очки" "screenshot" "снимок экрана" "+hud_panel_radar_maximized" "maximize radar (FIXME)" +"cl_cmd hud minigame" "toggle minigame menu (FIXME)" "" "" "" "Общение" "messagemode" "общий чат" diff --git a/qcsrc/client/command/cl_cmd.qc b/qcsrc/client/command/cl_cmd.qc index 7b74d2dd7..ecd05c16f 100644 --- a/qcsrc/client/command/cl_cmd.qc +++ b/qcsrc/client/command/cl_cmd.qc @@ -240,6 +240,15 @@ void LocalCommand_hud(int request, int argc) return; } + case "minigame": + { + if(HUD_MinigameMenu_IsOpened()) + HUD_MinigameMenu_Close(); + else + HUD_MinigameMenu_Open(); + return; + } + case "save": { if(argv(2)) @@ -285,7 +294,7 @@ void LocalCommand_hud(int request, int argc) print(" 'configname' is the name to save to for \"save\" action,\n"); print(" 'radartoggle' is to control hud_panel_radar_maximized for \"radar\" action,\n"); print(" and 'layout' is how to organize the scoreboard columns for the set action.\n"); - print(" Full list of commands here: \"configure, save, scoreboard_columns_help, scoreboard_columns_set, radar.\"\n"); + print(" Full list of commands here: \"configure, minigame, save, scoreboard_columns_help, scoreboard_columns_set, radar.\"\n"); return; } } diff --git a/qcsrc/client/hud.qc b/qcsrc/client/hud.qc index 024a0761e..073789a08 100644 --- a/qcsrc/client/hud.qc +++ b/qcsrc/client/hud.qc @@ -4447,12 +4447,30 @@ void HUD_Buffs(void) } +// Minigame +// +#include "../common/minigames/cl_minigames_hud.qc" + /* ================== Main HUD system ================== */ +float HUD_Panel_CheckFlags(float showflags) +{ + if ( HUD_Minigame_Showpanels() ) + return showflags & PANEL_SHOW_MINIGAME; + return showflags & PANEL_SHOW_MAINGAME; +} + +void HUD_Panel_Draw(entity panent) +{ + panel = panent; + if ( HUD_Panel_CheckFlags(panel.panel_showflags) ) + panel.panel_draw(); +} + void HUD_Reset (void) { // reset gametype specific icons @@ -4481,12 +4499,17 @@ void HUD_Main (void) // they must fade only when the menu does if(scoreboard_fade_alpha == 1) { - (panel = HUD_PANEL(CENTERPRINT)).panel_draw(); + HUD_Panel_Draw(HUD_PANEL(CENTERPRINT)); return; } if(!autocvar__hud_configure && !hud_fade_alpha) + { + hud_fade_alpha = 1; + HUD_Panel_Draw(HUD_PANEL(VOTE)); + hud_fade_alpha = 0; return; + } // Drawing stuff if (hud_skin_prev != autocvar_hud_skin) @@ -4586,14 +4609,14 @@ void HUD_Main (void) hud_draw_maximized = 0; // draw panels in order specified by panel_order array for(i = HUD_PANEL_NUM - 1; i >= 0; --i) - (panel = hud_panel[panel_order[i]]).panel_draw(); + HUD_Panel_Draw(hud_panel[panel_order[i]]); hud_draw_maximized = 1; // panels that may be maximized must check this var // draw maximized panels on top if(hud_panel_radar_maximized) - (panel = HUD_PANEL(RADAR)).panel_draw(); + HUD_Panel_Draw(HUD_PANEL(RADAR)); if(autocvar__con_chat_maximized) - (panel = HUD_PANEL(CHAT)).panel_draw(); + HUD_Panel_Draw(HUD_PANEL(CHAT)); HUD_Configure_PostDraw(); diff --git a/qcsrc/client/hud.qh b/qcsrc/client/hud.qh index 16a7645fd..10cc1bc3c 100644 --- a/qcsrc/client/hud.qh +++ b/qcsrc/client/hud.qh @@ -102,29 +102,39 @@ string panel_bg_padding_str; float current_player; float GetPlayerColorForce(int i); - +float GetPlayerColor(int i); +.float panel_showflags; +const float PANEL_SHOW_NEVER = 0x00; +const float PANEL_SHOW_MAINGAME = 0x01; +const float PANEL_SHOW_MINIGAME = 0x02; +const float PANEL_SHOW_ALWAYS = 0xff; +float HUD_Panel_CheckFlags(float showflags); #define HUD_PANELS(HUD_PANEL) \ - HUD_PANEL(WEAPONS , HUD_Weapons , weapons) \ - HUD_PANEL(AMMO , HUD_Ammo , ammo) \ - HUD_PANEL(POWERUPS , HUD_Powerups , powerups) \ - HUD_PANEL(HEALTHARMOR , HUD_HealthArmor , healtharmor) \ - HUD_PANEL(NOTIFY , HUD_Notify , notify) \ - HUD_PANEL(TIMER , HUD_Timer , timer) \ - HUD_PANEL(RADAR , HUD_Radar , radar) \ - HUD_PANEL(SCORE , HUD_Score , score) \ - HUD_PANEL(RACETIMER , HUD_RaceTimer , racetimer) \ - HUD_PANEL(VOTE , HUD_Vote , vote) \ - HUD_PANEL(MODICONS , HUD_ModIcons , modicons) \ - HUD_PANEL(PRESSEDKEYS , HUD_PressedKeys , pressedkeys) \ - HUD_PANEL(CHAT , HUD_Chat , chat) \ - HUD_PANEL(ENGINEINFO , HUD_EngineInfo , engineinfo) \ - HUD_PANEL(INFOMESSAGES , HUD_InfoMessages , infomessages) \ - HUD_PANEL(PHYSICS , HUD_Physics , physics) \ - HUD_PANEL(CENTERPRINT , HUD_CenterPrint , centerprint) \ - HUD_PANEL(BUFFS , HUD_Buffs , buffs) - -#define HUD_PANEL(NAME, draw_func, name) \ + HUD_PANEL(WEAPONS , HUD_Weapons , weapons, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(AMMO , HUD_Ammo , ammo, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(POWERUPS , HUD_Powerups , powerups, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(HEALTHARMOR , HUD_HealthArmor , healtharmor, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(NOTIFY , HUD_Notify , notify, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(TIMER , HUD_Timer , timer, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(RADAR , HUD_Radar , radar, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(SCORE , HUD_Score , score, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(RACETIMER , HUD_RaceTimer , racetimer, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(VOTE , HUD_Vote , vote, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(MODICONS , HUD_ModIcons , modicons, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(PRESSEDKEYS , HUD_PressedKeys , pressedkeys, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(CHAT , HUD_Chat , chat, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(ENGINEINFO , HUD_EngineInfo , engineinfo, PANEL_SHOW_ALWAYS ) \ + HUD_PANEL(INFOMESSAGES , HUD_InfoMessages , infomessages, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(PHYSICS , HUD_Physics , physics, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(CENTERPRINT , HUD_CenterPrint , centerprint, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(BUFFS , HUD_Buffs , buffs, PANEL_SHOW_MAINGAME ) \ + HUD_PANEL(MINIGAME_BOARD, HUD_MinigameBoard ,minigameboard, PANEL_SHOW_MINIGAME ) \ + HUD_PANEL(MINIGAME_STATUS,HUD_MinigameStatus,minigamestatus,PANEL_SHOW_MINIGAME ) \ + HUD_PANEL(MINIGAME_HELP, HUD_MinigameHelp ,minigamehelp, PANEL_SHOW_MINIGAME ) \ + HUD_PANEL(MINIGAME_MENU, HUD_MinigameMenu ,minigamemenu, PANEL_SHOW_ALWAYS ) + +#define HUD_PANEL(NAME, draw_func, name, showflags) \ int HUD_PANEL_##NAME; \ void draw_func(void); \ void RegisterHUD_Panel_##NAME() { \ @@ -134,7 +144,8 @@ float GetPlayerColorForce(int i); hud_panelent.classname = "hud_panel"; \ hud_panelent.panel_name = #name; \ hud_panelent.panel_id = HUD_PANEL_##NAME; \ - hud_panelent.panel_draw = draw_func; \ + hud_panelent.panel_draw = draw_func; \ + hud_panelent.panel_showflags = showflags; \ HUD_PANEL_NUM++; \ } \ ACCUMULATE_FUNCTION(RegisterHUD_Panels, RegisterHUD_Panel_##NAME); diff --git a/qcsrc/client/main.qc b/qcsrc/client/main.qc index 13f9545fa..78b45c666 100644 --- a/qcsrc/client/main.qc +++ b/qcsrc/client/main.qc @@ -115,6 +115,8 @@ void CSQC_Init(void) CALL_ACCUMULATED_FUNCTION(RegisterHUD_Panels); CALL_ACCUMULATED_FUNCTION(RegisterBuffs); + initialize_minigames(); + WaypointSprite_Load(); // precaches @@ -192,6 +194,9 @@ void Shutdown(void) if (!(calledhooks & HOOK_END)) localcmd("\ncl_hook_gameend\n"); } + + deactivate_minigame(); + HUD_MinigameMenu_Close(); } .float has_team; @@ -338,6 +343,9 @@ float CSQC_InputEvent(float bInputType, float nPrimary, float nSecondary) if (MapVote_InputEvent(bInputType, nPrimary, nSecondary)) return true; + if (HUD_Minigame_InputEvent(bInputType, nPrimary, nSecondary)) + return true; + if(menu_visible && menu_action) if(menu_action(bInputType, nPrimary, nSecondary)) return true; @@ -842,6 +850,7 @@ void CSQC_Ent_Update(float bIsNewEntity) case ENT_CLIENT_SPAWNEVENT: Ent_ReadSpawnEvent(bIsNewEntity); break; case ENT_CLIENT_NOTIFICATION: Read_Notification(bIsNewEntity); break; case ENT_CLIENT_HEALING_ORB: ent_healer(); break; + case ENT_CLIENT_MINIGAME: ent_read_minigame(); break; default: //error(strcat(_("unknown entity type in CSQC_Ent_Update: %d\n"), self.enttype)); diff --git a/qcsrc/client/progs.src b/qcsrc/client/progs.src index f80da18d6..fb9ff9ad8 100644 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@ -55,6 +55,9 @@ weapons/projectile.qc // TODO ../common/command/markup.qc ../common/command/rpn.qc +../common/minigames/minigames.qc +../common/minigames/cl_minigames.qc + ../common/monsters/monsters.qc ../common/weapons/weapons.qc // TODO diff --git a/qcsrc/client/scoreboard.qc b/qcsrc/client/scoreboard.qc index c6d871807..fa8d660ed 100644 --- a/qcsrc/client/scoreboard.qc +++ b/qcsrc/client/scoreboard.qc @@ -1,4 +1,5 @@ #include "scoreboard.qh" +#include "../common/minigames/cl_minigames.qh" float scoreboard_alpha_bg; float scoreboard_alpha_fg; @@ -959,7 +960,7 @@ float HUD_WouldDrawScoreboard() { return 1; else if (intermission == 2) return 0; - else if (spectatee_status != -1 && getstati(STAT_HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS) + else if (spectatee_status != -1 && getstati(STAT_HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame) return 1; else if (scoreboard_showscores_force) return 1; diff --git a/qcsrc/client/view.qc b/qcsrc/client/view.qc index ee8ef320a..03b240770 100644 --- a/qcsrc/client/view.qc +++ b/qcsrc/client/view.qc @@ -544,7 +544,9 @@ void UpdateCrosshair() CSQC_common_hud(); // crosshair goes VERY LAST - if(!scoreboard_active && !camera_active && intermission != 2 && spectatee_status != -1 && hud == HUD_NORMAL) + if(!scoreboard_active && !camera_active && intermission != 2 && + spectatee_status != -1 && hud == HUD_NORMAL && + !HUD_MinigameMenu_IsOpened() ) { if (!autocvar_crosshair_enabled) // main toggle for crosshair rendering return; @@ -1774,6 +1776,8 @@ void CSQC_UpdateView(float w, float h) if(autocvar__hud_configure) HUD_Panel_Mouse(); + if ( HUD_MinigameMenu_IsOpened() || minigame_isactive() ) + HUD_Minigame_Mouse(); if(hud && !intermission) { diff --git a/qcsrc/common/constants.qh b/qcsrc/common/constants.qh index 45a65abbe..b4e5a72ad 100644 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@ -78,6 +78,7 @@ const int ENT_CLIENT_ELIMINATEDPLAYERS = 39; const int ENT_CLIENT_TURRET = 40; const int ENT_CLIENT_AUXILIARYXHAIR = 50; const int ENT_CLIENT_VEHICLE = 60; +const int ENT_CLIENT_MINIGAME = 75; const int ENT_CLIENT_HEALING_ORB = 80; diff --git a/qcsrc/common/minigames/cl_minigames.qc b/qcsrc/common/minigames/cl_minigames.qc new file mode 100644 index 000000000..f6af765b7 --- /dev/null +++ b/qcsrc/common/minigames/cl_minigames.qc @@ -0,0 +1,401 @@ +#include "cl_minigames.qh" + +// Draw a square in the center of the avaliable area +void minigame_hud_simpleboard(vector pos, vector mySize, string board_texture) +{ + if(panel.current_panel_bg != "0" && panel.current_panel_bg != "") + draw_BorderPicture(pos - '1 1 0' * panel_bg_border, + panel.current_panel_bg, + mySize + '1 1 0' * 2 * panel_bg_border, + panel_bg_color, panel_bg_alpha, + '1 1 0' * (panel_bg_border/BORDER_MULTIPLIER)); + drawpic(pos, board_texture, mySize, '1 1 1', panel_bg_alpha, DRAWFLAG_NORMAL); +} + +// De-normalize (2D vector) v from relative coordinate inside pos mySize +vector minigame_hud_denormalize(vector v, vector pos, vector mySize) +{ + v_x = pos_x + v_x * mySize_x; + v_y = pos_y + v_y * mySize_y; + return v; +} +// De-normalize (2D vector) v from relative size inside pos mySize +vector minigame_hud_denormalize_size(vector v, vector pos, vector mySize) +{ + v_x = v_x * mySize_x; + v_y = v_y * mySize_y; + return v; +} + +// Normalize (2D vector) v to relative coordinate inside pos mySize +vector minigame_hud_normalize(vector v, vector pos, vector mySize) +{ + v_x = ( v_x - pos_x ) / mySize_x; + v_y = ( v_y - pos_y ) / mySize_y; + return v; +} + +// Check if the mouse is inside the given area +float minigame_hud_mouse_in(vector pos, vector sz) +{ + return mousepos_x >= pos_x && mousepos_x < pos_x + sz_x && + mousepos_y >= pos_y && mousepos_y < pos_y + sz_y ; +} + +void initialize_minigames() +{ + entity last_minig = world; + entity minig; + #define MINIGAME(name,nicename) \ + minig = spawn(); \ + minig.classname = "minigame_descriptor"; \ + minig.netname = strzone(strtolower(#name)); \ + minig.message = nicename; \ + minig.minigame_hud_board = minigame_hud_board_##name; \ + minig.minigame_hud_status = minigame_hud_status_##name; \ + minig.minigame_event = minigame_event_##name; \ + if ( !last_minig ) minigame_descriptors = minig; \ + else last_minig.list_next = minig; \ + last_minig = minig; + + REGISTERED_MINIGAMES + + #undef MINIGAME +} + +string minigame_texture_skin(string skinname, string name) +{ + return sprintf("gfx/hud/%s/minigames/%s", skinname, name); +} +string minigame_texture(string name) +{ + string path = minigame_texture_skin(autocvar_menu_skin,name); + if ( precache_pic(path) == "" ) + path = minigame_texture_skin("default", name); + return path; +} + +#define FIELD(Flags, Type, Name) MSLE_CLEAN_##Type(self.Name) +#define MSLE_CLEAN_String(x) strunzone(x); +#define MSLE_CLEAN_Byte(x) +#define MSLE_CLEAN_Char(x) +#define MSLE_CLEAN_Short(x) +#define MSLE_CLEAN_Coord(x) +#define MSLE_CLEAN_Angle(x) +#define MSLE_CLEAN_Float(x) +#define MSLE_CLEAN_Vector(x) +#define MSLE_CLEAN_Vector2D(x) + +#define MSLE(Name,Fields) \ + void msle_entremove_##Name() { strunzone(self.netname); Fields } +MINIGAME_SIMPLELINKED_ENTITIES +#undef MSLE +#undef FIELD + +void minigame_autoclean_entity(entity e) +{ + dprint("CL Auto-cleaned: ",ftos(num_for_edict(e)), " (",e.classname,")\n"); + remove(e); +} + +void HUD_MinigameMenu_CurrentButton(); +float auto_close_minigamemenu; +void deactivate_minigame() +{ + if ( !active_minigame ) + return; + + active_minigame.minigame_event(active_minigame,"deactivate"); + entity e = world; + while( (e = findentity(e, owner, self)) ) + if ( e.minigame_autoclean ) + { + minigame_autoclean_entity(e); + } + + minigame_self = world; + active_minigame = world; + + if ( auto_close_minigamemenu ) + { + HUD_MinigameMenu_Close(); + auto_close_minigamemenu = 0; + } + else + HUD_MinigameMenu_CurrentButton(); +} + +void activate_minigame(entity minigame) +{ + if ( !minigame ) + { + deactivate_minigame(); + return; + } + + if ( !minigame.descriptor || minigame.classname != "minigame" ) + { + dprint("Trying to activate unregistered minigame ",minigame.netname," in client\n"); + return; + } + + if ( minigame == active_minigame ) + return; + + if ( active_minigame ) + { + entity olds = minigame_self; + deactivate_minigame(); + minigame_self = olds; + } + + if ( minigame_self.owner != minigame ) + minigame_self = world; + active_minigame = minigame; + active_minigame.minigame_event(active_minigame,"activate"); + + if ( HUD_MinigameMenu_IsOpened() ) + HUD_MinigameMenu_CurrentButton(); + else + { + auto_close_minigamemenu = 1; + HUD_MinigameMenu_Open(); + } +} + +void minigame_player_entremove() +{ + if ( self.owner == active_minigame && self.minigame_playerslot == player_localentnum ) + deactivate_minigame(); +} + +vector ReadVector2D() { vector v; v_x = ReadCoord(); v_y = ReadCoord(); v_z = 0; return v; } +vector ReadVector() { vector v; v_x = ReadCoord(); v_y = ReadCoord(); v_z = ReadCoord(); return v; } +string() ReadString_Raw = #366; +string ReadString_Zoned() { return strzone(ReadString_Raw()); } +#define ReadFloat ReadCoord +#define ReadString ReadString_Zoned +#define FIELD(Flags, Type,Name) if ( sf & (Flags) ) self.Name = Read##Type(); +#define MSLE(Name,Fields) \ + else if ( self.classname == #Name ) { \ + if ( sf & MINIG_SF_CREATE ) { \ + minigame_read_owner(); \ + self.entremove = msle_entremove_##Name; \ + } \ + minigame_ent = self.owner; \ + Fields \ + } +void minigame_read_owner() +{ + string owner_name = ReadString_Raw(); + self.owner = world; + do + self.owner = find(self.owner,netname,owner_name); + while ( self.owner && self.owner.classname != "minigame" ); + if ( !self.owner ) + dprint("Got a minigame entity without a minigame!\n"); +} +void ent_read_minigame() +{ + float sf = ReadByte(); + if ( sf & MINIG_SF_CREATE ) + { + self.classname = msle_classname(ReadShort()); + self.netname = ReadString_Zoned(); + } + + entity minigame_ent = world; + + if ( self.classname == "minigame" ) + { + minigame_ent = self; + + if ( sf & MINIG_SF_CREATE ) + { + self.entremove = deactivate_minigame; + self.descriptor = minigame_get_descriptor(ReadString_Raw()); + if ( !self.descriptor ) + dprint("Got a minigame without a client-side descriptor!\n"); + else + self.minigame_event = self.descriptor.minigame_event; + } + if ( sf & MINIG_SF_UPDATE ) + self.minigame_flags = ReadLong(); + } + else if ( self.classname == "minigame_player" ) + { + float activate = 0; + if ( sf & MINIG_SF_CREATE ) + { + self.entremove = minigame_player_entremove; + minigame_read_owner(); + float ent = ReadLong(); + self.minigame_playerslot = ent; + dprint("Player: ",GetPlayerName(ent-1),"\n"); + + activate = (ent == player_localnum+1 && self.owner && self.owner != active_minigame); + + } + minigame_ent = self.owner; + + if ( sf & MINIG_SF_UPDATE ) + self.team = ReadByte(); + + if ( activate ) + { + minigame_self = self; + activate_minigame(self.owner); + } + } + MINIGAME_SIMPLELINKED_ENTITIES + + if ( minigame_ent ) + minigame_ent.minigame_event(minigame_ent,"network_receive",self,sf); + + dprint("CL Reading entity: ",ftos(num_for_edict(self)), + " classname:",self.classname," enttype:",ftos(self.enttype) ); + dprint(" sf:",ftos(sf)," netname:",self.netname,"\n\n"); +} +#undef ReadFloat +#undef ReadString +#undef FIELD +#undef MSLE + +string minigame_getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw) +{ + float last_word; + string s; + float take_until; + float skip = 0; + + s = getWrappedLine_remaining; + + if(w <= 0) + { + getWrappedLine_remaining = string_null; + return s; // the line has no size ANYWAY, nothing would be displayed. + } + + take_until = textLengthUpToWidth(s, w, theFontSize, tw); + + if ( take_until > strlen(s) ) + take_until = strlen(s); + + float i; + for ( i = 0; i < take_until; i++ ) + if ( substring(s,i,1) == "\n" ) + { + take_until = i; + skip = 1; + break; + } + + if ( take_until > 0 || skip > 0 ) + { + if ( skip == 0 && take_until < strlen(s) ) + { + last_word = take_until; + while(last_word > 0 && substring(s, last_word, 1) != " ") + --last_word; + + if ( last_word != 0 ) + { + take_until = last_word; + skip = 1; + } + } + + getWrappedLine_remaining = substring(s, take_until+skip, strlen(s) - (take_until+skip)); + if(getWrappedLine_remaining == "") + getWrappedLine_remaining = string_null; + else if (tw("^7", theFontSize) == 0) + getWrappedLine_remaining = strcat(find_last_color_code(substring(s, 0, take_until)), getWrappedLine_remaining); + return substring(s, 0, take_until); + } + else + { + getWrappedLine_remaining = string_null; + return s; + } +} + +vector minigame_drawstring_wrapped( float maxwidth, vector pos, string text, + vector fontsize, vector color, float theAlpha, float drawflags, float align ) +{ + getWrappedLine_remaining = text; + vector mypos = pos; + while ( getWrappedLine_remaining ) + { + string line = minigame_getWrappedLine(maxwidth,fontsize,stringwidth_nocolors); + if ( line == "" ) + break; + mypos_x = pos_x + (maxwidth - stringwidth_nocolors(line, fontsize)) * align; + drawstring(mypos, line, fontsize, color, theAlpha, drawflags); + mypos_y += fontsize_y; + } + mypos_x = maxwidth; + mypos_y -= pos_y; + return mypos; +} + +vector minigame_drawcolorcodedstring_wrapped( float maxwidth, vector pos, + string text, vector fontsize, float theAlpha, float drawflags, float align ) +{ + getWrappedLine_remaining = text; + vector mypos = pos; + while ( getWrappedLine_remaining ) + { + string line = minigame_getWrappedLine(maxwidth,fontsize,stringwidth_colors); + if ( line == "" ) + break; + mypos_x = pos_x + (maxwidth - stringwidth_colors(line, fontsize)) * align; + drawcolorcodedstring(mypos, line, fontsize, theAlpha, drawflags); + mypos_y += fontsize_y; + } + mypos_x = maxwidth; + mypos_y -= pos_y; + return mypos; +} + +void minigame_drawstring_trunc(float maxwidth, vector pos, string text, + vector fontsize, vector color, float theAlpha, float drawflags ) +{ + string line = textShortenToWidth(text,maxwidth,fontsize,stringwidth_nocolors); + drawstring(pos, line, fontsize, color, theAlpha, drawflags); +} + +void minigame_drawcolorcodedstring_trunc(float maxwidth, vector pos, string text, + vector fontsize, float theAlpha, float drawflags ) +{ + string line = textShortenToWidth(text,maxwidth,fontsize,stringwidth_colors); + drawcolorcodedstring(pos, line, fontsize, theAlpha, drawflags); +} + +void minigame_drawpic_centered( vector pos, string texture, vector sz, + vector color, float thealpha, float drawflags ) +{ + drawpic( pos-sz/2, texture, sz, color, thealpha, drawflags ); +} + +// Workaround because otherwise variadic arguments won't work properly +// It could be a bug in the compiler or in darkplaces +void minigame_cmd_workaround(float dummy, string...cmdargc) +{ + string cmd; + cmd = "cmd minigame "; + float i; + for ( i = 0; i < cmdargc; i++ ) + cmd = strcat(cmd,...(i,string)); + localcmd(strcat(cmd,"\n")); +} + +// Prompt the player to play in the current minigame +// (ie: it's their turn and they should get back to the minigame) +void minigame_prompt() +{ + if ( active_minigame && ! HUD_MinigameMenu_IsOpened() ) + { + HUD_Notify_Push(sprintf("minigames/%s/icon_notif",active_minigame.descriptor.netname), + _("It's your turn"), ""); + } +} \ No newline at end of file diff --git a/qcsrc/common/minigames/cl_minigames.qh b/qcsrc/common/minigames/cl_minigames.qh new file mode 100644 index 000000000..404f24a61 --- /dev/null +++ b/qcsrc/common/minigames/cl_minigames.qh @@ -0,0 +1,119 @@ +#ifndef CL_MINIGAMES_H +#define CL_MINIGAMES_H + +// Get a square in the center of the avaliable area +// \note macro to pass by reference pos and mySize +#define minigame_hud_fitsqare(pos, mySize) \ + if ( mySize##_x > mySize##_y ) \ + { \ + pos##_x += (mySize##_x-mySize##_y)/2; \ + mySize##_x = mySize##_y; \ + } \ + else \ + { \ + pos##_y += (mySize##_y-mySize##_x)/2; \ + mySize##_x = mySize##_x; \ + } \ + if(panel_bg_padding) \ + { \ + pos += '1 1 0' * panel_bg_padding; \ + mySize -= '2 2 0' * panel_bg_padding; \ + } + +// Get position and size of a panel +// \note macro to pass by reference pos and mySize +#define minigame_hud_panelarea(pos, mySize, panelID) \ + pos = stov(cvar_string(strcat("hud_panel_", HUD_PANEL(panelID).panel_name, "_pos"))); \ + mySize = stov(cvar_string(strcat("hud_panel_", HUD_PANEL(panelID).panel_name, "_size"))); \ + pos##_x *= vid_conwidth; pos##_y *= vid_conheight; \ + mySize##_x *= vid_conwidth; mySize##_y *= vid_conheight; + +// draw a panel border and the given texture +void minigame_hud_simpleboard(vector pos, vector mySize, string board_texture); + +// Normalize (2D vector) v to relative coordinate inside pos mySize +vector minigame_hud_normalize(vector v, vector pos, vector mySize); + +// De-normalize (2D vector) v from relative coordinate inside pos mySize +vector minigame_hud_denormalize(vector v, vector pos, vector mySize); + +// De-normalize (2D vector) v from relative size inside pos mySize +vector minigame_hud_denormalize_size(vector v, vector pos, vector mySize); + +// Check if the mouse is inside the given area +float minigame_hud_mouse_in(vector pos, vector sz); + +// Like drawstring, but wrapping words to fit maxwidth +// returns the size of the drawn area +// align selects the string alignment (0 = left, 0.5 = center, 1 = right) +vector minigame_drawstring_wrapped( float maxwidth, vector pos, string text, + vector fontsize, vector color, float theAlpha, float drawflags, float align ); + +// Like drawcolorcodedstring, but wrapping words to fit maxwidth +// returns the size of the drawn area +// align selects the string alignment (0 = left, 0.5 = center, 1 = right) +vector minigame_drawcolorcodedstring_wrapped( float maxwidth, vector pos, + string text, vector fontsize, float theAlpha, float drawflags, float align ); + +// Like drawstring but truncates the text to fit maxwidth +void minigame_drawstring_trunc(float maxwidth, vector pos, string text, + vector fontsize, vector color, float theAlpha, float drawflags ); + +// Like drawcolorcodedstring but truncates the text to fit maxwidth +void minigame_drawcolorcodedstring_trunc(float maxwidth, vector pos, string text, + vector fontsize, float theAlpha, float drawflags ); + +// like drawpic but pos represent the center rather than the topleft corner +void minigame_drawpic_centered( vector pos, string texture, vector sz, + vector color, float thealpha, float drawflags ); + +// Get full path of a minigame texture +string minigame_texture(string name); + +// For minigame descriptors: hud function for the game board +.void(vector pos, vector size) minigame_hud_board; +// For minigame descriptors: hud function for the game status +.void(vector pos, vector size) minigame_hud_status; +// For minigame_player: player server slot, don't use for anything else +.float minigame_playerslot; + +// register all minigames +void initialize_minigames(); + +// client-side minigame session cleanup +void deactivate_minigame(); + +// Currently active minigame session +entity active_minigame; +// minigame_player representing this client +entity minigame_self; + +// Whethere there's an active minigame +float minigame_isactive() +{ + return active_minigame != world; +} + +// Execute a minigame command +#define minigame_cmd(...) minigame_cmd_workaround(0,__VA_ARGS__) +void minigame_cmd_workaround(float dummy, string...cmdargc); + +// Read a minigame entity from the server +void ent_read_minigame(); + +// Prompt the player to play in the current minigame +// (ie: it's their turn and they should get back to the minigame) +void minigame_prompt(); + +float HUD_MinigameMenu_IsOpened(); +void HUD_MinigameMenu_Close(); +float HUD_Minigame_Showpanels(); +// Adds a game-specific entry to the menu +void HUD_MinigameMenu_CustomEntry(entity parent, string message, string event_arg); + + +#define FOREACH_MINIGAME_ENTITY(entityvar) \ + entityvar=world; \ + while( (entityvar = findentity(entityvar,owner,active_minigame)) ) + +#endif diff --git a/qcsrc/common/minigames/cl_minigames_hud.qc b/qcsrc/common/minigames/cl_minigames_hud.qc new file mode 100644 index 000000000..7df760979 --- /dev/null +++ b/qcsrc/common/minigames/cl_minigames_hud.qc @@ -0,0 +1,699 @@ +#include "minigames.qh" +#include "../../client/mapvoting.qh" + +float HUD_mouse_over(entity somepanel) +{ + vector pos = stov(cvar_string(strcat("hud_panel_", somepanel.panel_name, "_pos"))); + vector sz = stov(cvar_string(strcat("hud_panel_", somepanel.panel_name, "_size"))); + return mousepos_x >= pos_x*vid_conwidth && mousepos_x <= (pos_x+sz_x)*vid_conwidth && + mousepos_y >= pos_y*vid_conheight && mousepos_y <= (pos_y+sz_y)*vid_conheight ; +} + +// ==================================================================== +// Minigame Board +// ==================================================================== + +void HUD_MinigameBoard () +{ + entity hud_minigame = world; + + if(!autocvar__hud_configure) + hud_minigame = active_minigame.descriptor; + else + hud_minigame = minigame_get_descriptor("nmm"); + + if ( !hud_minigame ) + return; + + HUD_Panel_UpdateCvars(); + + + vector pos, mySize; + pos = panel_pos; + mySize = panel_size; + + hud_minigame.minigame_hud_board(pos,mySize); +} + +// ==================================================================== +// Minigame Status +// ==================================================================== +void HUD_MinigameStatus () +{ + entity hud_minigame = world; + + if(!autocvar__hud_configure) + hud_minigame = active_minigame.descriptor; + else + hud_minigame = minigame_get_descriptor("nmm"); + + if ( !hud_minigame ) + return; + + HUD_Panel_UpdateCvars(); + + + vector pos, mySize; + pos = panel_pos; + mySize = panel_size; + + if(panel_bg_padding) + { + pos += '1 1 0' * panel_bg_padding; + mySize -= '2 2 0' * panel_bg_padding; + } + + hud_minigame.minigame_hud_status(pos,mySize); +} + +// ==================================================================== +// Minigame Menu +// ==================================================================== + +// Minigame menu options: list head +entity HUD_MinigameMenu_entries; +// Minigame menu options: list tail +entity HUD_MinigameMenu_last_entry; + +// Minigame menu options: insert entry after the given location +void HUD_MinigameMenu_InsertEntry(entity new, entity prev) +{ + if ( !HUD_MinigameMenu_entries ) + { + HUD_MinigameMenu_entries = new; + HUD_MinigameMenu_last_entry = new; + return; + } + + new.list_prev = prev; + new.list_next = prev.list_next; + if ( prev.list_next ) + prev.list_next.list_prev = new; + else + HUD_MinigameMenu_last_entry = new; + prev.list_next = new; + +} + + +// minigame menu item uder the mouse +entity HUD_MinigameMenu_activeitem; + +// Click the given item +void HUD_MinigameMenu_Click(entity menuitem) +{ + if ( menuitem ) + { + entity e = self; + self = menuitem; + menuitem.use(); + self = e; + } +} + +// Minigame menu options: Remove the given entry +// Precondition: the given entry is actually in the list +void HUD_MinigameMenu_EraseEntry ( entity e ) +{ + // remove child items (if any) + if ( e.flags & 2 ) + { + HUD_MinigameMenu_Click(e); + } + + if ( e.list_prev ) + e.list_prev.list_next = e.list_next; + else + HUD_MinigameMenu_entries = e.list_next; + + if ( e.list_next ) + e.list_next.list_prev = e.list_prev; + else + HUD_MinigameMenu_last_entry = e.list_prev; + + if ( HUD_MinigameMenu_activeitem == e ) + HUD_MinigameMenu_activeitem = world; + + remove(e); +} + +// Minigame menu options: create entry +entity HUD_MinigameMenu_SpawnEntry(string s, vector offset, vector fontsize, vector color,void() click) +{ + entity entry = spawn(); + entry.message = s; + entry.origin = offset; + entry.size = fontsize; + entry.colormod = color; + entry.flags = 0; + entry.use = click; + panel_pos_y += fontsize_y; + return entry; +} + +// Spawn a child entry of a collapsable entry +entity HUD_MinigameMenu_SpawnSubEntry(string s, void() click, entity parent) +{ + vector item_fontsize = hud_fontsize*1.25; + vector item_offset = '1 0 0' * item_fontsize_x; + entity item = HUD_MinigameMenu_SpawnEntry( + s,item_offset,item_fontsize,'0.8 0.8 0.8', click ); + item.owner = parent; + return item; +} + +// Click action for Create sub-entries +void HUD_MinigameMenu_ClickCreate_Entry() +{ + minigame_cmd("create ",self.netname); +} + +// Helper click action for collapsible entries +// returns true when you have to create the sub-entries +float HUD_MinigameMenu_Click_ExpandCollapse() +{ + entity e; + if ( self.flags & 2 ) + { + if ( HUD_MinigameMenu_activeitem && + HUD_MinigameMenu_activeitem.owner == self ) + HUD_MinigameMenu_activeitem = world; + self.flags &= ~2; + for ( e = self.list_next; e != world && e.owner == self; e = self.list_next ) + { + if ( e.flags & 2 ) + HUD_MinigameMenu_Click(e); + self.list_next = e.list_next; + remove(e); + } + if ( self.list_next ) + self.list_next.list_prev = self; + } + else + { + for ( e = HUD_MinigameMenu_entries; e != world; e = e.list_next ) + { + if ( e.flags & 2 && e.origin_x == self.origin_x) + HUD_MinigameMenu_Click(e); + } + + self.flags |= 2; + + return true; + } + return false; +} + +// Click action for the Create menu +void HUD_MinigameMenu_ClickCreate() +{ + if ( HUD_MinigameMenu_Click_ExpandCollapse() ) + { + entity e; + entity curr; + entity prev = self; + for ( e = minigame_descriptors; e != world; e = e.list_next ) + { + curr = HUD_MinigameMenu_SpawnSubEntry( + e.message, HUD_MinigameMenu_ClickCreate_Entry, self ); + curr.netname = e.netname; + curr.model = strzone(minigame_texture(strcat(e.netname,"/icon"))); + HUD_MinigameMenu_InsertEntry( curr, prev ); + prev = curr; + } + } +} + +// Click action for Join sub-entries +void HUD_MinigameMenu_ClickJoin_Entry() +{ + minigame_cmd("join ",self.netname); + HUD_MinigameMenu_EraseEntry(self); +} + +// Click action for the Join menu +void HUD_MinigameMenu_ClickJoin() +{ + if ( HUD_MinigameMenu_Click_ExpandCollapse() ) + { + entity e = world; + entity curr; + entity prev = self; + while( (e = find(e,classname,"minigame")) ) + { + if ( e != active_minigame ) + { + curr = HUD_MinigameMenu_SpawnSubEntry( + e.netname, HUD_MinigameMenu_ClickJoin_Entry, self ); + curr.netname = e.netname; + curr.model = strzone(minigame_texture(strcat(e.descriptor.netname,"/icon"))); + HUD_MinigameMenu_InsertEntry( curr, prev ); + prev = curr; + } + } + } +} + +/*// Temporary placeholder for un-implemented Click actions +void HUD_MinigameMenu_ClickNoop() +{ + dprint("Placeholder for ",self.message,"\n"); +}*/ + +// Click action for Quit +void HUD_MinigameMenu_ClickQuit() +{ + minigame_cmd("end"); +} + +// Click action for Invite sub-entries +void HUD_MinigameMenu_ClickInvite_Entry() +{ + minigame_cmd("invite #",self.netname); +} + +// Click action for the Invite menu +void HUD_MinigameMenu_ClickInvite() +{ + if ( HUD_MinigameMenu_Click_ExpandCollapse() ) + { + float i; + entity e; + entity prev = self; + for(i = 0; i < maxclients; ++i) + { + if ( player_localnum != i && playerslots[i] && GetPlayerName(i) != "" && + !findfloat(world,minigame_playerslot,i+1) && playerslots[i].ping ) + { + e = HUD_MinigameMenu_SpawnSubEntry( + strzone(GetPlayerName(i)), HUD_MinigameMenu_ClickInvite_Entry, + self ); + e.flags |= 1; + e.netname = strzone(ftos(i+1)); + e.origin_x *= 2; + HUD_MinigameMenu_InsertEntry(e,prev); + prev = e; + } + } + } +} + +void HUD_MinigameMenu_ClickCustomEntry() +{ + if ( active_minigame ) + active_minigame.minigame_event(active_minigame,"menu_click",self.netname); +} + +// Adds a game-specific entry to the menu +void HUD_MinigameMenu_CustomEntry(entity parent, string menumessage, string event_arg) +{ + entity e = HUD_MinigameMenu_SpawnSubEntry( + menumessage, HUD_MinigameMenu_ClickCustomEntry, parent ); + e.netname = event_arg; + HUD_MinigameMenu_InsertEntry(e, parent); + dprint("CustomEntry ",ftos(num_for_edict(parent))," ",menumessage," ",event_arg,"\n"); +} + +// Click action for the Current Game menu +void HUD_MinigameMenu_ClickCurrentGame() +{ + if ( HUD_MinigameMenu_Click_ExpandCollapse() ) + { + HUD_MinigameMenu_InsertEntry( HUD_MinigameMenu_SpawnSubEntry( + _("Quit"), HUD_MinigameMenu_ClickQuit, self ), self); + + active_minigame.minigame_event(active_minigame,"menu_show",self); + + HUD_MinigameMenu_InsertEntry( HUD_MinigameMenu_SpawnSubEntry( + _("Invite"), HUD_MinigameMenu_ClickInvite, self), self); + } +} +// Whether the minigame menu panel is open +float HUD_MinigameMenu_IsOpened() +{ + return !!HUD_MinigameMenu_entries; +} + +// Close the minigame menu panel +void HUD_MinigameMenu_Close() +{ + if ( HUD_MinigameMenu_IsOpened() ) + { + entity e, p; + for ( e = HUD_MinigameMenu_entries; e != world; e = p ) + { + p = e.list_next; + remove(e); + } + HUD_MinigameMenu_entries = world; + HUD_MinigameMenu_last_entry = world; + HUD_MinigameMenu_activeitem = world; + if(autocvar_hud_cursormode) + if ( !autocvar__hud_configure ) + setcursormode(0); + } +} + +// toggle a button to manage the current game +void HUD_MinigameMenu_CurrentButton() +{ + entity e; + if ( active_minigame ) + { + for ( e = HUD_MinigameMenu_last_entry; e != world; e = e.list_prev ) + if ( e.classname == "hud_minigamemenu_exit" ) + { + HUD_MinigameMenu_EraseEntry(e); + break; + } + entity currb = HUD_MinigameMenu_SpawnEntry( + _("Current Game"), '0 0 0', hud_fontsize*1.5,'0.7 0.84 1', HUD_MinigameMenu_ClickCurrentGame ); + currb.classname = "hud_minigamemenu_current"; + currb.model = strzone(minigame_texture(strcat(active_minigame.descriptor.netname,"/icon"))); + HUD_MinigameMenu_InsertEntry(currb,HUD_MinigameMenu_last_entry); + HUD_MinigameMenu_Click(currb); + } + else + { + entity p; + for ( e = HUD_MinigameMenu_last_entry; e != world; e = p.list_prev ) + { + p = e; + if ( e.classname == "hud_minigamemenu_current" ) + { + p = e.list_next; + if ( !p ) + p = HUD_MinigameMenu_last_entry; + HUD_MinigameMenu_EraseEntry(e); + break; + } + } + for ( e = HUD_MinigameMenu_last_entry; e != world; e = e.list_prev ) + if ( e.classname == "hud_minigamemenu_exit" ) + return; + entity exit = HUD_MinigameMenu_SpawnEntry( + _("Exit Menu"),'0 0 0',hud_fontsize*1.5,'0.7 0.84 1', HUD_MinigameMenu_Close); + exit.classname = "hud_minigamemenu_exit"; + HUD_MinigameMenu_InsertEntry ( exit, HUD_MinigameMenu_last_entry ); + } +} + +// Open the minigame menu panel +void HUD_MinigameMenu_Open() +{ + if ( !HUD_MinigameMenu_IsOpened() ) + { + HUD_MinigameMenu_InsertEntry( HUD_MinigameMenu_SpawnEntry( + _("Create"), '0 0 0', hud_fontsize*1.5,'0.7 0.84 1', HUD_MinigameMenu_ClickCreate), + HUD_MinigameMenu_last_entry ); + HUD_MinigameMenu_InsertEntry ( HUD_MinigameMenu_SpawnEntry( + _("Join"),'0 0 0',hud_fontsize*1.5,'0.7 0.84 1', HUD_MinigameMenu_ClickJoin), + HUD_MinigameMenu_last_entry ); + HUD_MinigameMenu_CurrentButton(); + HUD_MinigameMenu_activeitem = world; + if(autocvar_hud_cursormode) + setcursormode(1); + } +} + +// Handles mouse input on to minigame menu panel +void HUD_MinigameMenu_MouseInput() +{ + panel = HUD_PANEL(MINIGAME_MENU); + + HUD_Panel_UpdateCvars(); + + if(panel_bg_padding) + { + panel_pos += '1 1 0' * panel_bg_padding; + panel_size -= '2 2 0' * panel_bg_padding; + } + + entity e; + + panel_pos_y += hud_fontsize_y*2; + + HUD_MinigameMenu_activeitem = world; + vector sz; + for ( e = HUD_MinigameMenu_entries; e != world; e = e.list_next ) + { + sz = eX*panel_size_x + eY*e.size_y; + if ( e.model ) + sz_y = 22; + if ( !HUD_MinigameMenu_activeitem && mousepos_y >= panel_pos_y && mousepos_y <= panel_pos_y + sz_y ) + { + HUD_MinigameMenu_activeitem = e; + } + panel_pos_y += sz_y; + } +} + +// Draw a menu entry +void HUD_MinigameMenu_DrawEntry(vector pos, string s, vector fontsize, vector color) +{ + minigame_drawstring_trunc(panel_size_x-pos_x+panel_pos_x, pos, s, + fontsize, color, panel_fg_alpha, DRAWFLAG_NORMAL); +} +// Draw a color-coded menu +void HUD_MinigameMenu_DrawColoredEntry(vector pos, string s, vector fontsize) +{ + minigame_drawcolorcodedstring_trunc(panel_size_x-pos_x+panel_pos_x, pos, s, + fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); +} + +// minigame menu panel UI +void HUD_MinigameMenu () +{ + if ( !HUD_MinigameMenu_IsOpened() ) + return; + + HUD_Panel_UpdateCvars(); + + HUD_Panel_DrawBg(1); + + if(panel_bg_padding) + { + panel_pos += '1 1 0' * panel_bg_padding; + panel_size -= '2 2 0' * panel_bg_padding; + } + + HUD_MinigameMenu_DrawEntry(panel_pos,_("Minigames"),hud_fontsize*2,'0.25 0.47 0.72'); + panel_pos_y += hud_fontsize_y*2; + + entity e; + vector color; + vector offset; + float itemh; + vector imgsz = '22 22 0'; // NOTE: if changed, edit where HUD_MinigameMenu_activeitem is selected + for ( e = HUD_MinigameMenu_entries; e != world; e = e.list_next ) + { + color = e.colormod; + + offset = e.origin; + itemh = e.size_y; + + if ( e.model ) + itemh = imgsz_y; + + if ( e.flags & 2 ) + { + drawfill(panel_pos, eX*panel_size_x + eY*itemh, e.colormod, + panel_fg_alpha, DRAWFLAG_NORMAL); + color = '0 0 0'; + } + + if ( e.model ) + { + drawpic( panel_pos+offset, e.model, imgsz, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL ); + offset_x += imgsz_x; + offset_y = (imgsz_y-e.size_y) / 2; + } + + if ( e.flags & 1 ) + HUD_MinigameMenu_DrawColoredEntry(panel_pos+offset,e.message,e.size); + else + HUD_MinigameMenu_DrawEntry(panel_pos+offset,e.message,e.size,color); + + if ( e == HUD_MinigameMenu_activeitem ) + drawfill(panel_pos, eX*panel_size_x + eY*itemh,'1 1 1', 0.25, DRAWFLAG_ADDITIVE); + + panel_pos_y += itemh; + } +} + +// ==================================================================== +// Minigame Help Panel +// ==================================================================== + +void HUD_MinigameHelp() +{ + string help_message; + + if(!autocvar__hud_configure) + help_message = active_minigame.message; + else + help_message = "Minigame message"; + + if ( !help_message ) + return; + + HUD_Panel_UpdateCvars(); + + + vector pos, mySize; + pos = panel_pos; + mySize = panel_size; + + if(panel_bg_padding) + { + pos += '1 1 0' * panel_bg_padding; + mySize -= '2 2 0' * panel_bg_padding; + } + + minigame_drawcolorcodedstring_wrapped( mySize_x, pos, help_message, + hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL, 0.5 ); +} + +// ==================================================================== +// Minigame Panel Input +// ==================================================================== +float HUD_Minigame_InputEvent(float bInputType, float nPrimary, float nSecondary) +{ + + if( !HUD_MinigameMenu_IsOpened() || autocvar__hud_configure ) + return false; + + if(bInputType == 3) + { + mousepos_x = nPrimary; + mousepos_y = nSecondary; + if ( minigame_isactive() && HUD_mouse_over(HUD_PANEL(MINIGAME_BOARD)) ) + active_minigame.minigame_event(active_minigame,"mouse_moved",mousepos); + return true; + + } + else + { + + if(bInputType == 0) { + if(nPrimary == K_ALT) hudShiftState |= S_ALT; + if(nPrimary == K_CTRL) hudShiftState |= S_CTRL; + if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT; + if(nPrimary == K_MOUSE1) mouseClicked |= S_MOUSE1; + if(nPrimary == K_MOUSE2) mouseClicked |= S_MOUSE2; + } + else if(bInputType == 1) { + if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT); + if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL); + if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT); + if(nPrimary == K_MOUSE1) mouseClicked -= (mouseClicked & S_MOUSE1); + if(nPrimary == K_MOUSE2) mouseClicked -= (mouseClicked & S_MOUSE2); + } + + // allow some binds + string con_keys; + float keys; + float i; + con_keys = findkeysforcommand("toggleconsole", 0); + keys = tokenize(con_keys); // findkeysforcommand returns data for this + for (i = 0; i < keys; ++i) + { + if(nPrimary == stof(argv(i))) + return false; + } + + if ( minigame_isactive() && ( bInputType == 0 || bInputType == 1 ) ) + { + string device = ""; + string action = bInputType == 0 ? "pressed" : "released"; + if ( nPrimary >= K_MOUSE1 && nPrimary <= K_MOUSE16 ) + { + if ( HUD_mouse_over(HUD_PANEL(MINIGAME_BOARD)) ) + device = "mouse"; + } + else + device = "key"; + + if ( device && active_minigame.minigame_event( + active_minigame,strcat(device,"_",action),nPrimary) ) + return true; + + /// TODO: bInputType == 2? + } + + if ( bInputType == 0 ) + { + if ( nPrimary == K_MOUSE1 && HUD_MinigameMenu_activeitem && + HUD_mouse_over(HUD_PANEL(MINIGAME_MENU)) ) + { + HUD_MinigameMenu_Click(HUD_MinigameMenu_activeitem); + return true; + } + if ( nPrimary == K_UPARROW || nPrimary == K_KP_UPARROW ) + { + if ( HUD_MinigameMenu_activeitem && HUD_MinigameMenu_activeitem.list_prev ) + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_activeitem.list_prev; + else + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_last_entry; + return true; + } + else if ( nPrimary == K_DOWNARROW || nPrimary == K_KP_DOWNARROW ) + { + if ( HUD_MinigameMenu_activeitem && HUD_MinigameMenu_activeitem.list_next ) + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_activeitem.list_next; + else + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_entries; + return true; + } + else if ( nPrimary == K_HOME || nPrimary == K_KP_HOME ) + { + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_entries; + return true; + } + else if ( nPrimary == K_END || nPrimary == K_KP_END ) + { + HUD_MinigameMenu_activeitem = HUD_MinigameMenu_entries; + return true; + } + else if ( nPrimary == K_KP_ENTER || nPrimary == K_ENTER || nPrimary == K_SPACE ) + { + HUD_MinigameMenu_Click(HUD_MinigameMenu_activeitem); + return true; + } + else if ( nPrimary == K_ESCAPE ) + { + HUD_MinigameMenu_Close(); + return true; + } + } + } + + return false; + +} + +void HUD_Minigame_Mouse() +{ + if( !HUD_MinigameMenu_IsOpened() || autocvar__hud_configure || mv_active ) + return; + + if(!autocvar_hud_cursormode) + { + mousepos = mousepos + getmousepos() * autocvar_menu_mouse_speed; + + mousepos_x = bound(0, mousepos_x, vid_conwidth); + mousepos_y = bound(0, mousepos_y, vid_conheight); + } + + if ( HUD_MinigameMenu_IsOpened() && HUD_mouse_over(HUD_PANEL(MINIGAME_MENU)) ) + HUD_MinigameMenu_MouseInput(); + + vector cursorsize = '32 32 0'; + drawpic(mousepos-'8 4 0', strcat("gfx/menu/", autocvar_menu_skin, "/cursor.tga"), + cursorsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); +} + +float HUD_Minigame_Showpanels() +{ + return HUD_MinigameMenu_IsOpened() && ( autocvar__hud_configure || minigame_isactive() ); +} diff --git a/qcsrc/common/minigames/minigame/all.qh b/qcsrc/common/minigames/minigame/all.qh new file mode 100644 index 000000000..e550ff468 --- /dev/null +++ b/qcsrc/common/minigames/minigame/all.qh @@ -0,0 +1,105 @@ +#if defined(SVQC) +#include "../sv_minigames.qh" +#elif defined(CSQC) +#include "../cl_minigames.qh" +#endif + +/** + +How to create a minigame +======================== + +Create a file for your minigame in this directory and #include it here. +(ttt.qc implements tic tac toe and can be used as an example) +and add your minigame to REGISTERED_MINIGAMES (see below) + +Required functions +------------------ + +SVQC: + float minigame_event_(entity minigame, string event, ...count) + see ../minigames.qh for a detailed explanation +CSQC: + void minigame_hud_board_(vector pos, vector mySize) + draws the main game board inside the rectangle defined by pos and mySize + (That rectangle is expressed in window coordinates) + void minigame_hud_status_(vector pos, vector mySize) + draws the game status panel inside the rectangle defined by pos and mySize + (That rectangle is expressed in window coordinates) + This panel shows eg scores, captured pieces and so on + float minigame_event_(entity minigame, string event, ...count) + see ../minigames.qh for a detailed explanation + +Managing entities +----------------- + +You can link entities without having to worry about them if their classname +has been defined in MINIGAME_SIMPLELINKED_ENTITIES (see below) +Such entities can be spawned with msle_spawn and the system +will handle networking and cleanup automatically. +You'll still need to set .SendFlags according to what you specified in FIELD +in order for them to be sent, ../minigames.qh defines some constants to be +used as send flags for minigame entities: + +* MINIG_SF_CREATE + Used when creating a new object, you can use this to define fields that don't change +* MINIG_SF_UPDATE + A miscellaneous update, can be safely used if the entity has just a few fields +* MINIG_SF_CUSTOM + Starting value for custom flags, since there are bit-wise flags, + the following values shall be MINIG_SF_CUSTOM*2, MINIG_SF_CUSTOM*4 and MINIG_SF_CUSTOM*8. +* MINIG_SF_MAX + Maximum flag value that will be networked +* MINIG_SF_ALL + Mask matching all possible flags + +Note: As of now, flags are sent as a single byte + +Even for non-networked entities, the system provides a system to remove +automatically unneeded entities when the minigame is over, the requirement is +that .owner is set to the minigame session entity and .minigame_autoclean is true. +*/ + +#include "nmm.qc" +#include "ttt.qc" + +/** + * Registration: + * MINIGAME(id,"Name") + * id (QuakeC symbol) Game identifier, used to find the functions explained above + * "Name"(String) Human readable name for the game, shown in the UI + */ +#define REGISTERED_MINIGAMES \ + MINIGAME(nmm, "Nine Men's Morris") \ + MINIGAME(ttt, "Tic Tac Toe") \ + /*empty line*/ + +/** + * Set up automatic entity read/write functionality + * To ensure that everything is handled automatically, spawn on the server using msle_spawn + * Syntax: + * MSLE(classname,Field...) \ + * classname: Identifier used to recognize the type of the entity + * (must be set as .classname on the sent entities) + * Field... : List of FIELD calls + * FIELD(sendflags, Type, field) + * sendflags: Send flags that signal when this field has to be sent + * Type : Type of the entity field. Used to determine WriteX/ReadX functions. + * Follows a list of accepted values + * Byte + * Char + * Short + * Long + * Coord + * Angle + * String Note: strzoned on client + * Float Note: implemented as Write/Read Coord + * Vector Note: implemented as Write/Read Coord on _x _y _z + * Vector2D Note: implemented as Write/Read Coord on _x _y + * Note: + * classname and netname are always sent + * MSLE stands for Minigame Simple Linked Entity + */ +#define MINIGAME_SIMPLELINKED_ENTITIES \ + MSLE(minigame_board_piece,FIELD(MINIG_SF_CREATE,Byte,team) FIELD(MINIG_SF_UPDATE, Short, minigame_flags) FIELD(MINIG_SF_UPDATE, Vector2D,origin)) \ + /*empty line*/ diff --git a/qcsrc/common/minigames/minigame/nmm.qc b/qcsrc/common/minigames/minigame/nmm.qc new file mode 100644 index 000000000..aaebe690e --- /dev/null +++ b/qcsrc/common/minigames/minigame/nmm.qc @@ -0,0 +1,762 @@ +const float NMM_TURN_PLACE = 0x0100; // player has to place a piece on the board +const float NMM_TURN_MOVE = 0x0200; // player has to move a piece by one tile +const float NMM_TURN_FLY = 0x0400; // player has to move a piece anywhere +const float NMM_TURN_TAKE = 0x0800; // player has to take a non-mill piece +const float NMM_TURN_TAKEANY=0x1000; // combine with NMM_TURN_TAKE, can take mill pieces +const float NMM_TURN_WIN = 0x2000; // player has won +const float NMM_TURN_TYPE = 0xff00; +const float NMM_TURN_TEAM1 = 0x0001; +const float NMM_TURN_TEAM2 = 0x0002; +const float NMM_TURN_TEAM = 0x00ff; + +const float NMM_PIECE_DEAD = 0x0; // captured by the enemy +const float NMM_PIECE_HOME = 0x1; // not yet placed +const float NMM_PIECE_BOARD = 0x2; // placed on the board + +.float nmm_tile_distance; +.entity nmm_tile_piece; +.string nmm_tile_hmill; +.string nmm_tile_vmill; + +// build a string containing the indices of the tile to check for a horizontal mill +string nmm_tile_build_hmill(entity tile) +{ + float number = minigame_tile_number(tile.netname); + float letter = minigame_tile_letter(tile.netname); + if ( number == letter || number+letter == 6 ) + { + float add = letter < 3 ? 1 : -1; + return strcat(tile.netname," ", + minigame_tile_buildname(letter+add*tile.nmm_tile_distance,number)," ", + minigame_tile_buildname(letter+add*2*tile.nmm_tile_distance,number) ); + } + else if ( letter == 3 ) + return strcat(minigame_tile_buildname(letter-tile.nmm_tile_distance,number)," ", + tile.netname," ", + minigame_tile_buildname(letter+tile.nmm_tile_distance,number) ); + else if ( letter < 3 ) + return strcat(minigame_tile_buildname(0,number)," ", + minigame_tile_buildname(1,number)," ", + minigame_tile_buildname(2,number) ); + else + return strcat(minigame_tile_buildname(4,number)," ", + minigame_tile_buildname(5,number)," ", + minigame_tile_buildname(6,number) ); +} + +// build a string containing the indices of the tile to check for a vertical mill +string nmm_tile_build_vmill(entity tile) +{ + float letter = minigame_tile_letter(tile.netname); + float number = minigame_tile_number(tile.netname); + if ( letter == number || letter+number == 6 ) + { + float add = number < 3 ? 1 : -1; + return strcat(tile.netname," ", + minigame_tile_buildname(letter,number+add*tile.nmm_tile_distance)," ", + minigame_tile_buildname(letter,number+add*2*tile.nmm_tile_distance) ); + } + else if ( number == 3 ) + return strcat(minigame_tile_buildname(letter,number-tile.nmm_tile_distance)," ", + tile.netname," ", + minigame_tile_buildname(letter,number+tile.nmm_tile_distance) ); + else if ( number < 3 ) + return strcat(minigame_tile_buildname(letter,0)," ", + minigame_tile_buildname(letter,1)," ", + minigame_tile_buildname(letter,2) ); + else + return strcat(minigame_tile_buildname(letter,4)," ", + minigame_tile_buildname(letter,5)," ", + minigame_tile_buildname(letter,6) ); +} + +// Create an new tile +// \param id Tile index (eg: a1) +// \param minig Owner minigame instance +// \param distance Distance from adjacent tiles +void nmm_spawn_tile(string id, entity minig, float distance) +{ + // TODO global variable + list_next for simpler tile loops + entity e = spawn(); + e.origin = minigame_tile_pos(id,7,7); + e.classname = "minigame_nmm_tile"; + e.netname = id; + e.owner = minig; + e.team = 0; + e.nmm_tile_distance = distance; + e.nmm_tile_hmill = strzone(nmm_tile_build_hmill(e)); + e.nmm_tile_vmill = strzone(nmm_tile_build_vmill(e)); +} + +// Create a tile square and recursively create inner squares +// \param minig Owner minigame instance +// \param offset Index offset (eg: 1 to start the square at b2, 0 at a1 etc.) +// \param skip Number of indices to skip between tiles (eg 1: a1, a3) +void nmm_spawn_tile_square( entity minig, float offset, float skip ) +{ + float letter = offset; + float number = offset; + float i, j; + for ( i = 0; i < 3; i++ ) + { + number = offset; + for ( j = 0; j < 3; j++ ) + { + if ( i != 1 || j != 1 ) + nmm_spawn_tile(strzone(minigame_tile_buildname(letter,number)),minig, skip+1); + number += skip+1; + } + letter += skip+1; + } + + if ( skip > 0 ) + nmm_spawn_tile_square(minig,offset+1,skip-1); +} + +// Remove tiles of a NMM minigame +void nmm_kill_tiles(entity minig) +{ + entity e = world; + while ( ( e = findentity(e,owner,minig) ) ) + if ( e.classname == "minigame_nmm_tile" ) + { + strunzone(e.netname); + strunzone(e.nmm_tile_hmill); + strunzone(e.nmm_tile_vmill); + remove(e); + } +} + +// Create the tiles of a NMM minigame +void nmm_init_tiles(entity minig) +{ + nmm_spawn_tile_square(minig,0,2); +} + +// Find a tile by its id +entity nmm_find_tile(entity minig, string id) +{ + entity e = world; + while ( ( e = findentity(e,owner,minig) ) ) + if ( e.classname == "minigame_nmm_tile" && e.netname == id ) + return e; + return world; +} + +// Check whether two tiles are adjacent +float nmm_tile_adjacent(entity tile1, entity tile2) +{ + + float dnumber = fabs ( minigame_tile_number(tile1.netname) - minigame_tile_number(tile2.netname) ); + float dletter = fabs ( minigame_tile_letter(tile1.netname) - minigame_tile_letter(tile2.netname) ); + + return ( dnumber == 0 && ( dletter == 1 || dletter == tile1.nmm_tile_distance ) ) || + ( dletter == 0 && ( dnumber == 1 || dnumber == tile1.nmm_tile_distance ) ); +} + +// Returns 1 if there is at least 1 free adjacent tile +float nmm_tile_canmove(entity tile) +{ + entity e = world; + while ( ( e = findentity(e,owner,tile.owner) ) ) + if ( e.classname == "minigame_nmm_tile" && !e.nmm_tile_piece + && nmm_tile_adjacent(e,tile) ) + { + return 1; + } + return 0; +} + +// Check if the given tile id appears in the string +float nmm_in_mill_string(entity tile, string s) +{ + float argc = tokenize(s); + float i; + for ( i = 0; i < argc; i++ ) + { + entity e = nmm_find_tile(tile.owner,argv(i)); + if ( !e || !e.nmm_tile_piece || e.nmm_tile_piece.team != tile.nmm_tile_piece.team ) + return 0; + } + return 1; +} + +// Check if a tile is in a mill +float nmm_in_mill(entity tile) +{ + return tile.nmm_tile_piece && ( + nmm_in_mill_string(tile,tile.nmm_tile_hmill) || + nmm_in_mill_string(tile,tile.nmm_tile_vmill) ); +} + + +#ifdef SVQC +// Find a NMM piece matching some of the given flags and team number +entity nmm_find_piece(entity start, entity minigame, float teamn, float pieceflags) +{ + entity e = start; + while ( ( e = findentity(e,owner,minigame) ) ) + if ( e.classname == "minigame_board_piece" && + (e.minigame_flags & pieceflags) && e.team == teamn ) + return e; + return world; +} + +// Count NMM pieces matching flags and team number +float nmm_count_pieces(entity minigame, float teamn, float pieceflags) +{ + float n = 0; + entity e = world; + while (( e = nmm_find_piece(e,minigame, teamn, pieceflags) )) + n++; + return n; +} + +// required function, handle server side events +float minigame_event_nmm(entity minigame, string event, ...) +{ + if ( event == "start" ) + { + minigame.minigame_flags = NMM_TURN_PLACE|NMM_TURN_TEAM1; + nmm_init_tiles(minigame); + float i; + entity e; + for ( i = 0; i < 7; i++ ) + { + e = msle_spawn(minigame,"minigame_board_piece"); + e.team = 1; + e.minigame_flags = NMM_PIECE_HOME; + e = msle_spawn(minigame,"minigame_board_piece"); + e.team = 2; + e.minigame_flags = NMM_PIECE_HOME; + } + + return 1; + } + else if ( event == "end" ) + { + nmm_kill_tiles(minigame); + } + else if ( event == "join" ) + { + float n = 0; + entity e; + for ( e = minigame.minigame_players; e; e = e.list_next ) + n++; + if ( n >= 2 ) + return 0; + if ( minigame.minigame_players && minigame.minigame_players.team == 1 ) + return 2; + return 1; + } + else if ( event == "cmd" ) + { + entity e = ...(0,entity); + float argc = ...(1,float); + entity tile = world; + entity piece = world; + float move_ok = 0; + + if ( e && argc >= 2 && argv(0) == "move" && + ( minigame.minigame_flags & NMM_TURN_TEAM ) == e.team ) + { + tile = nmm_find_tile(minigame,argv(1)); + if ( !tile ) + { + move_ok = 0; + } + else if ( minigame.minigame_flags & NMM_TURN_PLACE ) + { + piece = nmm_find_piece(world,minigame,e.team,NMM_PIECE_HOME); + if ( !tile.nmm_tile_piece && piece ) + { + tile.nmm_tile_piece = piece; + piece.minigame_flags = NMM_PIECE_BOARD; + piece.origin = tile.origin; + piece.SendFlags |= MINIG_SF_UPDATE; + move_ok = 1; + } + } + else if ( minigame.minigame_flags & NMM_TURN_MOVE ) + { + if ( tile.nmm_tile_piece && tile.nmm_tile_piece.team == e.team ) + { + piece = tile.nmm_tile_piece; + entity tile2 = nmm_find_tile(minigame,argv(2)); + if ( tile2 && nmm_tile_adjacent(tile,tile2) && !tile2.nmm_tile_piece ) + { + tile.nmm_tile_piece = world; + tile2.nmm_tile_piece = piece; + piece.origin = tile2.origin; + piece.SendFlags |= MINIG_SF_UPDATE; + tile = tile2; + move_ok = 1; + } + } + + } + else if ( minigame.minigame_flags & NMM_TURN_FLY ) + { + if ( tile.nmm_tile_piece && tile.nmm_tile_piece.team == e.team ) + { + piece = tile.nmm_tile_piece; + entity tile2 = nmm_find_tile(minigame,argv(2)); + if ( tile2 && !tile2.nmm_tile_piece ) + { + tile.nmm_tile_piece = world; + tile2.nmm_tile_piece = piece; + piece.origin = tile2.origin; + piece.SendFlags |= MINIG_SF_UPDATE; + tile = tile2; + move_ok = 1; + } + } + + } + else if ( minigame.minigame_flags & NMM_TURN_TAKE ) + { + piece = tile.nmm_tile_piece; + if ( piece && piece.nmm_tile_piece.team != e.team ) + { + tile.nmm_tile_piece = world; + piece.minigame_flags = NMM_PIECE_DEAD; + piece.SendFlags |= MINIG_SF_UPDATE; + move_ok = 1; + } + } + + float nextteam = e.team % 2 + 1; + float npieces = nmm_count_pieces(minigame,nextteam,NMM_PIECE_HOME|NMM_PIECE_BOARD); + + if ( npieces < 3 ) + { + minigame.minigame_flags = NMM_TURN_WIN | e.team; + minigame.SendFlags |= MINIG_SF_UPDATE; + } + else if ( move_ok) + { + if ( !(minigame.minigame_flags & NMM_TURN_TAKE) && nmm_in_mill(tile) ) + { + minigame.minigame_flags = NMM_TURN_TAKE|e.team; + float takemill = NMM_TURN_TAKEANY; + entity f = world; + while ( ( f = findentity(f,owner,minigame) ) ) + if ( f.classname == "minigame_nmm_tile" && f.nmm_tile_piece && + f.nmm_tile_piece.team == nextteam && !nmm_in_mill(f) ) + { + takemill = 0; + break; + } + minigame.minigame_flags |= takemill; + } + else + { + if ( nmm_find_piece(world,minigame,nextteam,NMM_PIECE_HOME) ) + minigame.minigame_flags = NMM_TURN_PLACE|nextteam; + else if ( npieces == 3 ) + minigame.minigame_flags = NMM_TURN_FLY|nextteam; + else + { + minigame.minigame_flags = NMM_TURN_WIN|e.team; + entity f = world; + while ( ( f = findentity(f,owner,minigame) ) ) + if ( f.classname == "minigame_nmm_tile" && f.nmm_tile_piece && + f.nmm_tile_piece.team == nextteam && nmm_tile_canmove(f) ) + { + minigame.minigame_flags = NMM_TURN_MOVE|nextteam; + break; + } + } + } + minigame.SendFlags |= MINIG_SF_UPDATE; + } + else + dprint("Invalid move: ",...(2,string),"\n"); + return 1; + } + } + return 0; +} + +#elif defined(CSQC) + +entity nmm_currtile; +entity nmm_fromtile; + +vector nmm_boardpos; +vector nmm_boardsize; + +// whether the given tile is a valid selection +float nmm_valid_selection(entity tile) +{ + if ( ( tile.owner.minigame_flags & NMM_TURN_TEAM ) != minigame_self.team ) + return 0; // not our turn + if ( tile.owner.minigame_flags & NMM_TURN_PLACE ) + return !tile.nmm_tile_piece; // need to put a piece on an empty spot + if ( tile.owner.minigame_flags & NMM_TURN_MOVE ) + { + if ( tile.nmm_tile_piece && tile.nmm_tile_piece.team == minigame_self.team && + nmm_tile_canmove(tile) ) + return 1; // movable tile + if ( nmm_fromtile ) // valid destination + return !tile.nmm_tile_piece && nmm_tile_adjacent(nmm_fromtile,tile); + return 0; + } + if ( tile.owner.minigame_flags & NMM_TURN_FLY ) + { + if ( nmm_fromtile ) + return !tile.nmm_tile_piece; + else + return tile.nmm_tile_piece && tile.nmm_tile_piece.team == minigame_self.team; + } + if ( tile.owner.minigame_flags & NMM_TURN_TAKE ) + return tile.nmm_tile_piece && tile.nmm_tile_piece.team != minigame_self.team && + ( (tile.owner.minigame_flags & NMM_TURN_TAKEANY) || !nmm_in_mill(tile) ); + return 0; +} + +// whether it should highlight valid tile selections +float nmm_draw_avaliable(entity tile) +{ + if ( ( tile.owner.minigame_flags & NMM_TURN_TEAM ) != minigame_self.team ) + return 0; + if ( (tile.owner.minigame_flags & NMM_TURN_TAKE) ) + return 1; + if ( (tile.owner.minigame_flags & (NMM_TURN_FLY|NMM_TURN_MOVE)) && nmm_fromtile ) + return !tile.nmm_tile_piece; + return 0; +} + +// Required function, draw the game board +void minigame_hud_board_nmm(vector pos, vector mySize) +{ + minigame_hud_fitsqare(pos, mySize); + nmm_boardpos = pos; + nmm_boardsize = mySize; + minigame_hud_simpleboard(pos,mySize,minigame_texture("nmm/board")); + + vector tile_size = minigame_hud_denormalize_size('1 1 0'/7,pos,mySize); + vector tile_pos; + entity e; + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_nmm_tile" ) + { + tile_pos = minigame_hud_denormalize(e.origin,pos,mySize); + + if ( e == nmm_fromtile ) + { + minigame_drawpic_centered( tile_pos, minigame_texture("nmm/tile_active"), + tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL ); + } + else if ( nmm_draw_avaliable(e) && nmm_valid_selection(e) ) + { + minigame_drawpic_centered( tile_pos, minigame_texture("nmm/tile_available"), + tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL ); + } + + if ( e == nmm_currtile ) + { + minigame_drawpic_centered( tile_pos, minigame_texture("nmm/tile_selected"), + tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_ADDITIVE ); + } + + if ( e.nmm_tile_piece ) + { + minigame_drawpic_centered( tile_pos, + minigame_texture(strcat("nmm/piece",ftos(e.nmm_tile_piece.team))), + tile_size*0.8, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL ); + } + + //drawstring(tile_pos, e.netname, hud_fontsize, '1 0 0', 1, DRAWFLAG_NORMAL); + } + } + + if ( active_minigame.minigame_flags & NMM_TURN_WIN ) + { + vector winfs = hud_fontsize*2; + string playername = ""; + FOREACH_MINIGAME_ENTITY(e) + if ( e.classname == "minigame_player" && + e.team == (active_minigame.minigame_flags & NMM_TURN_TEAM) ) + playername = GetPlayerName(e.minigame_playerslot-1); + + vector win_pos = pos+eY*(mySize_y-winfs_y)/2; + vector win_sz; + win_sz = minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos, + sprintf("%s^7 won the game!",playername), + winfs, 0, DRAWFLAG_NORMAL, 0.5); + + drawfill(win_pos-eY*hud_fontsize_y,win_sz+2*eY*hud_fontsize_y,'1 1 1',0.5,DRAWFLAG_ADDITIVE); + + minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos, + sprintf("%s^7 won the game!",playername), + winfs, panel_fg_alpha, DRAWFLAG_NORMAL, 0.5); + } +} + +// Required function, draw the game status panel +void minigame_hud_status_nmm(vector pos, vector mySize) +{ + HUD_Panel_DrawBg(1); + vector ts; + + ts = minigame_drawstring_wrapped(mySize_x,pos,active_minigame.descriptor.message, + hud_fontsize * 2, '0.25 0.47 0.72', panel_fg_alpha, DRAWFLAG_NORMAL,0.5); + pos_y += ts_y; + mySize_y -= ts_y; + + vector player_fontsize = hud_fontsize * 1.75; + ts_y = ( mySize_y - 2*player_fontsize_y ) / 2; + ts_x = mySize_x; + + float player1x = 0; + float player2x = 0; + vector piece_sz = '48 48 0'; + float piece_space = piece_sz_x + ( ts_x - 7 * piece_sz_x ) / 6; + vector mypos; + float piece_light = 1; + entity e = world; + + mypos = pos; + if ( (active_minigame.minigame_flags&NMM_TURN_TEAM) == 2 ) + mypos_y += player_fontsize_y + ts_y; + drawfill(mypos,eX*mySize_x+eY*player_fontsize_y,'1 1 1',0.5,DRAWFLAG_ADDITIVE); + mypos_y += player_fontsize_y; + drawfill(mypos,eX*mySize_x+eY*piece_sz_y,'1 1 1',0.25,DRAWFLAG_ADDITIVE); + + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_player" ) + { + mypos = pos; + if ( e.team == 2 ) + mypos_y += player_fontsize_y + ts_y; + minigame_drawcolorcodedstring_trunc(mySize_x,mypos, + GetPlayerName(e.minigame_playerslot-1), + player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); + } + else if ( e.classname == "minigame_board_piece" ) + { + mypos = pos; + mypos_y += player_fontsize_y; + if ( e.team == 2 ) + { + mypos_x += player2x; + player2x += piece_space; + mypos_y += player_fontsize_y + ts_y; + } + else + { + mypos_x += player1x; + player1x += piece_space; + } + if ( e.minigame_flags == NMM_PIECE_HOME ) + piece_light = 0.5; + else if ( e.minigame_flags == NMM_PIECE_BOARD ) + piece_light = 1; + else + piece_light = 0.15; + + drawpic(mypos, minigame_texture(strcat("nmm/piece",ftos(e.team))), piece_sz, + '1 1 1'*piece_light, panel_fg_alpha, DRAWFLAG_NORMAL ); + } + } +} + +// Make the correct move +void nmm_make_move(entity minigame) +{ + if ( nmm_currtile ) + { + if ( minigame.minigame_flags & (NMM_TURN_PLACE|NMM_TURN_TAKE) ) + { + minigame_cmd("move ",nmm_currtile.netname); + nmm_fromtile = world; + } + else if ( (minigame.minigame_flags & (NMM_TURN_MOVE|NMM_TURN_FLY)) ) + { + if ( nmm_fromtile == nmm_currtile ) + { + nmm_fromtile = world; + } + else if ( nmm_currtile.nmm_tile_piece && nmm_currtile.nmm_tile_piece.team == minigame_self.team ) + { + nmm_fromtile = nmm_currtile; + } + else if ( nmm_fromtile ) + { + minigame_cmd("move ",nmm_fromtile.netname," ",nmm_currtile.netname); + nmm_fromtile = world; + } + } + } + else + nmm_fromtile = world; +} + +string nmm_turn_to_string(float turnflags) +{ + if ( turnflags & NMM_TURN_WIN ) + { + if ( (turnflags&NMM_TURN_TEAM) != minigame_self.team ) + return _("You lost the game!"); + return _("You win!"); + } + + if ( (turnflags&NMM_TURN_TEAM) != minigame_self.team ) + return _("Wait for your opponent to make their move"); + if ( turnflags & NMM_TURN_PLACE ) + return _("Click on the game board to place your piece"); + if ( turnflags & NMM_TURN_MOVE ) + return _("You can select one of your pieces to move it in one of the surrounding places"); + if ( turnflags & NMM_TURN_FLY ) + return _("You can select one of your pieces to move it anywhere on the board"); + if ( turnflags & NMM_TURN_TAKE ) + return _("You can take one of the opponent's pieces"); + + return ""; +} + +// Required function, handle client events +float minigame_event_nmm(entity minigame, string event, ...) +{ + if ( event == "activate" ) + { + nmm_fromtile = world; + nmm_init_tiles(minigame); + minigame.message = nmm_turn_to_string(minigame.minigame_flags); + } + else if ( event == "deactivate" ) + { + nmm_fromtile = world; + nmm_kill_tiles(minigame); + } + else if ( event == "key_pressed" && (minigame.minigame_flags&NMM_TURN_TEAM) == minigame_self.team ) + { + switch ( ...(0,float) ) + { + case K_RIGHTARROW: + case K_KP_RIGHTARROW: + if ( ! nmm_currtile ) + nmm_currtile = nmm_find_tile(active_minigame,"a7"); + else + { + string tileid = nmm_currtile.netname; + nmm_currtile = world; + while ( !nmm_currtile ) + { + tileid = minigame_relative_tile(tileid,1,0,7,7); + nmm_currtile = nmm_find_tile(active_minigame,tileid); + } + } + return 1; + case K_LEFTARROW: + case K_KP_LEFTARROW: + if ( ! nmm_currtile ) + nmm_currtile = nmm_find_tile(active_minigame,"g7"); + else + { + string tileid = nmm_currtile.netname; + nmm_currtile = world; + while ( !nmm_currtile ) + { + tileid = minigame_relative_tile(tileid,-1,0,7,7); + nmm_currtile = nmm_find_tile(active_minigame,tileid); + } + } + return 1; + case K_UPARROW: + case K_KP_UPARROW: + if ( ! nmm_currtile ) + nmm_currtile = nmm_find_tile(active_minigame,"a1"); + else + { + string tileid = nmm_currtile.netname; + nmm_currtile = world; + while ( !nmm_currtile ) + { + tileid = minigame_relative_tile(tileid,0,1,7,7); + nmm_currtile = nmm_find_tile(active_minigame,tileid); + } + } + return 1; + case K_DOWNARROW: + case K_KP_DOWNARROW: + if ( ! nmm_currtile ) + nmm_currtile = nmm_find_tile(active_minigame,"a7"); + else + { + string tileid = nmm_currtile.netname; + nmm_currtile = world; + while ( !nmm_currtile ) + { + tileid = minigame_relative_tile(tileid,0,-1,7,7); + nmm_currtile = nmm_find_tile(active_minigame,tileid); + } + } + return 1; + case K_ENTER: + case K_KP_ENTER: + case K_SPACE: + nmm_make_move(minigame); + return 1; + } + return 0; + } + else if ( event == "mouse_pressed" && ...(0,float) == K_MOUSE1 ) + { + nmm_make_move(minigame); + return 1; + } + else if ( event == "mouse_moved" ) + { + nmm_currtile = world; + vector tile_pos; + vector tile_size = minigame_hud_denormalize_size('1 1 0'/7,nmm_boardpos,nmm_boardsize); + entity e; + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_nmm_tile" ) + { + tile_pos = minigame_hud_denormalize(e.origin,nmm_boardpos,nmm_boardsize)-tile_size/2; + if ( minigame_hud_mouse_in(tile_pos, tile_size) && nmm_valid_selection(e) ) + { + nmm_currtile = e; + break; + } + } + } + return 1; + } + else if ( event == "network_receive" ) + { + if ( self.classname == "minigame_board_piece" && ( ...(1,float) & MINIG_SF_UPDATE ) ) + { + entity e; + string tileid = ""; + if ( self.minigame_flags & NMM_PIECE_BOARD ) + tileid = minigame_tile_name(self.origin,7,7); + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_nmm_tile" ) + { + if ( e.nmm_tile_piece == self ) + e.nmm_tile_piece = world; + if ( e.netname == tileid ) + e.nmm_tile_piece = self; + } + } + } + else if ( self.classname == "minigame" && ( ...(1,float) & MINIG_SF_UPDATE ) ) + { + self.message = nmm_turn_to_string(self.minigame_flags); + if ( self.minigame_flags & minigame_self.team ) + minigame_prompt(); + } + } + + return 0; +} + +#endif diff --git a/qcsrc/common/minigames/minigame/ttt.qc b/qcsrc/common/minigames/minigame/ttt.qc new file mode 100644 index 000000000..e2dc0a06a --- /dev/null +++ b/qcsrc/common/minigames/minigame/ttt.qc @@ -0,0 +1,688 @@ +const float TTT_TURN_PLACE = 0x0100; // player has to place a piece on the board +const float TTT_TURN_WIN = 0x0200; // player has won +const float TTT_TURN_DRAW = 0x0400; // no moves are possible +const float TTT_TURN_NEXT = 0x0800; // a player wants to start a new match +const float TTT_TURN_TYPE = 0x0f00; // turn type mask + +const float TTT_TURN_TEAM1 = 0x0001; +const float TTT_TURN_TEAM2 = 0x0002; +const float TTT_TURN_TEAM = 0x000f; // turn team mask + +// send flags +const float TTT_SF_PLAYERSCORE = MINIG_SF_CUSTOM; // send minigame_player scores (won matches) +const float TTT_SF_SINGLEPLAYER = MINIG_SF_CUSTOM<<1;// send minigame.ttt_ai + +.float ttt_npieces; // (minigame) number of pieces on the board (simplifies checking a draw) +.float ttt_nexteam; // (minigame) next team (used to change the starting team on following matches) +.float ttt_ai; // (minigame) when non-zero, singleplayer vs AI + +// find tic tac toe piece given its tile name +entity ttt_find_piece(entity minig, string tile) +{ + entity e = world; + while ( ( e = findentity(e,owner,minig) ) ) + if ( e.classname == "minigame_board_piece" && e.netname == tile ) + return e; + return world; +} + +// Checks if the given piece completes a row +float ttt_winning_piece(entity piece) +{ + float number = minigame_tile_number(piece.netname); + float letter = minigame_tile_letter(piece.netname); + + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(0,number)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(1,number)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(2,number)).team == piece.team ) + return 1; + + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(letter,0)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(letter,1)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(letter,2)).team == piece.team ) + return 1; + + if ( number == letter ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(0,0)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(1,1)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(2,2)).team == piece.team ) + return 1; + + if ( number == 2-letter ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(0,2)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(1,1)).team == piece.team ) + if ( ttt_find_piece(piece.owner,minigame_tile_buildname(2,0)).team == piece.team ) + return 1; + + return 0; +} + +// check if the tile name is valid (3x3 grid) +float ttt_valid_tile(string tile) +{ + if ( !tile ) + return 0; + float number = minigame_tile_number(tile); + float letter = minigame_tile_letter(tile); + return 0 <= number && number < 3 && 0 <= letter && letter < 3; +} + +// make a move +void ttt_move(entity minigame, entity player, string pos ) +{ + if ( minigame.minigame_flags & TTT_TURN_PLACE ) + if ( pos && player.team == (minigame.minigame_flags & TTT_TURN_TEAM) ) + { + if ( ttt_valid_tile(pos) ) + if ( !ttt_find_piece(minigame,pos) ) + { + entity piece = msle_spawn(minigame,"minigame_board_piece"); + piece.team = player.team; + piece.netname = strzone(pos); + minigame_server_sendflags(piece,MINIG_SF_ALL); + minigame_server_sendflags(minigame,MINIG_SF_UPDATE); + minigame.ttt_npieces++; + minigame.ttt_nexteam = minigame_next_team(player.team,2); + if ( ttt_winning_piece(piece) ) + { + player.minigame_flags++; + minigame_server_sendflags(player, TTT_SF_PLAYERSCORE); + minigame.minigame_flags = TTT_TURN_WIN | player.team; + } + else if ( minigame.ttt_npieces >= 9 ) + minigame.minigame_flags = TTT_TURN_DRAW; + else + minigame.minigame_flags = TTT_TURN_PLACE | minigame.ttt_nexteam; + } + } +} + +// request a new match +void ttt_next_match(entity minigame, entity player) +{ +#ifdef SVQC + // on multiplayer matches, wait for both players to agree + if ( minigame.minigame_flags & (TTT_TURN_WIN|TTT_TURN_DRAW) ) + { + minigame.minigame_flags = TTT_TURN_NEXT | player.team; + minigame.SendFlags |= MINIG_SF_UPDATE; + } + else if ( (minigame.minigame_flags & TTT_TURN_NEXT) && + !( minigame.minigame_flags & player.team ) ) +#endif + { + minigame.minigame_flags = TTT_TURN_PLACE | minigame.ttt_nexteam; + minigame_server_sendflags(minigame,MINIG_SF_UPDATE); + minigame.ttt_npieces = 0; + entity e = world; + while ( ( e = findentity(e,owner,minigame) ) ) + if ( e.classname == "minigame_board_piece" ) + remove(e); + } +} + +#ifdef SVQC + + +// required function, handle server side events +float minigame_event_ttt(entity minigame, string event, ...) +{ + switch(event) + { + case "start": + { + minigame.minigame_flags = (TTT_TURN_PLACE | TTT_TURN_TEAM1); + return true; + } + case "end": + { + entity e = world; + while( (e = findentity(e, owner, minigame)) ) + if(e.classname == "minigame_board_piece") + { + if(e.netname) { strunzone(e.netname); } + remove(e); + } + return false; + } + case "join": + { + float pl_num = minigame_count_players(minigame); + + // Don't allow joining a single player match + if ( (minigame.ttt_ai) && pl_num > 0 ) + return false; + + // Don't allow more than 2 players + if(pl_num >= 2) { return false; } + + // Get the right team + if(minigame.minigame_players) + return minigame_next_team(minigame.minigame_players.team, 2); + + // Team 1 by default + return 1; + } + case "cmd": + { + switch(argv(0)) + { + case "move": + ttt_move(minigame, ...(0,entity), ...(1,float) == 2 ? argv(1) : string_null ); + return true; + case "next": + ttt_next_match(minigame,...(0,entity)); + return true; + case "singleplayer": + if ( minigame_count_players(minigame) == 1 ) + { + minigame.ttt_ai = minigame_next_team(minigame.minigame_players.team, 2); + minigame.SendFlags = TTT_SF_SINGLEPLAYER; + } + return true; + } + + return false; + } + case "network_send": + { + entity sent = ...(0,entity); + float sf = ...(1,float); + if ( sent.classname == "minigame_player" && (sf & TTT_SF_PLAYERSCORE ) ) + { + WriteByte(MSG_ENTITY,sent.minigame_flags); + } + else if ( sent.classname == "minigame" && (sf & TTT_SF_SINGLEPLAYER) ) + { + WriteByte(MSG_ENTITY,sent.ttt_ai); + } + return false; + } + } + + return false; +} + + +#elif defined(CSQC) + +string ttt_curr_pos; // identifier of the tile under the mouse +vector ttt_boardpos; // HUD board position +vector ttt_boardsize;// HUD board size +.float ttt_checkwin; // Used to optimize checks to display a win + +// Required function, draw the game board +void minigame_hud_board_ttt(vector pos, vector mySize) +{ + minigame_hud_fitsqare(pos, mySize); + ttt_boardpos = pos; + ttt_boardsize = mySize; + + minigame_hud_simpleboard(pos,mySize,minigame_texture("ttt/board")); + + vector tile_size = minigame_hud_denormalize_size('1 1 0'/3,pos,mySize); + vector tile_pos; + + if ( (active_minigame.minigame_flags & TTT_TURN_TEAM) == minigame_self.team ) + if ( ttt_valid_tile(ttt_curr_pos) ) + { + tile_pos = minigame_tile_pos(ttt_curr_pos,3,3); + tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize); + minigame_drawpic_centered( tile_pos, + minigame_texture(strcat("ttt/piece",ftos(minigame_self.team))), + tile_size, '1 1 1', panel_fg_alpha/2, DRAWFLAG_NORMAL ); + } + + entity e; + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_board_piece" ) + { + tile_pos = minigame_tile_pos(e.netname,3,3); + tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize); + + if ( active_minigame.minigame_flags & TTT_TURN_WIN ) + if ( !e.ttt_checkwin ) + e.ttt_checkwin = ttt_winning_piece(e) ? 1 : -1; + + float icon_color = 1; + if ( e.ttt_checkwin == -1 ) + icon_color = 0.4; + else if ( e.ttt_checkwin == 1 ) + { + icon_color = 2; + minigame_drawpic_centered( tile_pos, minigame_texture("ttt/winglow"), + tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_ADDITIVE ); + } + + minigame_drawpic_centered( tile_pos, + minigame_texture(strcat("ttt/piece",ftos(e.team))), + tile_size, '1 1 1'*icon_color, panel_fg_alpha, DRAWFLAG_NORMAL ); + } + } +} + + +// Required function, draw the game status panel +void minigame_hud_status_ttt(vector pos, vector mySize) +{ + HUD_Panel_DrawBg(1); + vector ts; + ts = minigame_drawstring_wrapped(mySize_x,pos,active_minigame.descriptor.message, + hud_fontsize * 2, '0.25 0.47 0.72', panel_fg_alpha, DRAWFLAG_NORMAL,0.5); + + pos_y += ts_y; + mySize_y -= ts_y; + + vector player_fontsize = hud_fontsize * 1.75; + ts_y = ( mySize_y - 2*player_fontsize_y ) / 2; + ts_x = mySize_x; + vector mypos; + vector tile_size = '48 48 0'; + + entity e; + FOREACH_MINIGAME_ENTITY(e) + { + if ( e.classname == "minigame_player" ) + { + mypos = pos; + if ( e.team == 2 ) + mypos_y += player_fontsize_y + ts_y; + minigame_drawcolorcodedstring_trunc(mySize_x,mypos, + (e.minigame_playerslot ? GetPlayerName(e.minigame_playerslot-1) : _("AI")), + player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); + + mypos_y += player_fontsize_y; + drawpic( mypos, + minigame_texture(strcat("ttt/piece",ftos(e.team))), + tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL ); + + mypos_x += tile_size_x; + + drawstring(mypos,ftos(e.minigame_flags),tile_size, + '0.7 0.84 1', panel_fg_alpha, DRAWFLAG_NORMAL); + } + } +} + +// Turn a set of flags into a help message +string ttt_turn_to_string(float turnflags) +{ + if ( turnflags & TTT_TURN_DRAW ) + return _("Draw"); + + if ( turnflags & TTT_TURN_WIN ) + { + if ( (turnflags&TTT_TURN_TEAM) != minigame_self.team ) + return _("You lost the game!\nSelect \"^1Next Match^7\" on the menu for a rematch!"); + return _("You win!\nSelect \"^1Next Match^7\" on the menu to start a new match!"); + } + + if ( turnflags & TTT_TURN_NEXT ) + { + if ( (turnflags&TTT_TURN_TEAM) != minigame_self.team ) + return _("Select \"^1Next Match^7\" on the menu to start a new match!"); + return _("Wait for your opponent to confirm the rematch"); + } + + if ( (turnflags & TTT_TURN_TEAM) != minigame_self.team ) + return _("Wait for your opponent to make their move"); + + if ( turnflags & TTT_TURN_PLACE ) + return _("Click on the game board to place your piece"); + + return ""; +} + +const float TTT_AI_POSFLAG_A1 = 0x0001; +const float TTT_AI_POSFLAG_A2 = 0x0002; +const float TTT_AI_POSFLAG_A3 = 0x0004; +const float TTT_AI_POSFLAG_B1 = 0x0008; +const float TTT_AI_POSFLAG_B2 = 0x0010; +const float TTT_AI_POSFLAG_B3 = 0x0020; +const float TTT_AI_POSFLAG_C1 = 0x0040; +const float TTT_AI_POSFLAG_C2 = 0x0080; +const float TTT_AI_POSFLAG_C3 = 0x0100; + +// convert a flag to a position +string ttt_ai_piece_flag2pos(float pieceflag) +{ + switch(pieceflag) + { + case TTT_AI_POSFLAG_A1: + return "a1"; + case TTT_AI_POSFLAG_A2: + return "a2"; + case TTT_AI_POSFLAG_A3: + return "a3"; + + case TTT_AI_POSFLAG_B1: + return "b1"; + case TTT_AI_POSFLAG_B2: + return "b2"; + case TTT_AI_POSFLAG_B3: + return "b3"; + + case TTT_AI_POSFLAG_C1: + return "c1"; + case TTT_AI_POSFLAG_C2: + return "c2"; + case TTT_AI_POSFLAG_C3: + return "c3"; + + default: + return string_null; + } +} + +float ttt_ai_checkmask(float piecemask, float checkflags) +{ + return checkflags && (piecemask & checkflags) == checkflags; +} + +// get the third flag if the mask matches two of them +float ttt_ai_1of3(float piecemask, float flag1, float flag2, float flag3) +{ + if ( ttt_ai_checkmask(piecemask,flag1|flag2|flag3) ) + return 0; + + if ( ttt_ai_checkmask(piecemask,flag1|flag2) ) + return flag3; + + if ( ttt_ai_checkmask(piecemask,flag3|flag2) ) + return flag1; + + if ( ttt_ai_checkmask(piecemask,flag3|flag1) ) + return flag2; + + return 0; +} + +// Select a random flag in the mask +float ttt_ai_random(float piecemask) +{ + if ( !piecemask ) + return 0; + + float i; + float f = 1; + + RandomSelection_Init(); + + for ( i = 0; i < 9; i++ ) + { + if ( piecemask & f ) + RandomSelection_Add(world, f, string_null, 1, 1); + f <<= 1; + } + + dprint(sprintf("TTT AI: selected %x from %x\n", + RandomSelection_chosen_float, piecemask) ); + return RandomSelection_chosen_float; +} + +// Block/complete a 3 i na row +float ttt_ai_block3 ( float piecemask, float piecemask_free ) +{ + float r = 0; + + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_A2,TTT_AI_POSFLAG_A3); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_B1,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_B3); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_C1,TTT_AI_POSFLAG_C2,TTT_AI_POSFLAG_C3); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_B1,TTT_AI_POSFLAG_C1); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A2,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C2); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A3,TTT_AI_POSFLAG_B3,TTT_AI_POSFLAG_C3); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A1,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C3); + r |= ttt_ai_1of3(piecemask,TTT_AI_POSFLAG_A3,TTT_AI_POSFLAG_B2,TTT_AI_POSFLAG_C1); + dprint(sprintf("TTT AI: possible 3 in a rows in %x: %x (%x)\n",piecemask,r, r&piecemask_free)); + r &= piecemask_free; + return ttt_ai_random(r); +} + +// Simple AI +// 1) tries to win the game if possible +// 2) tries to block the opponent if they have 2 in a row +// 3) places a piece randomly +string ttt_ai_choose_simple(float piecemask_self, float piecemask_opponent, float piecemask_free ) +{ + float move = 0; + + dprint("TTT AI: checking winning move\n"); + if (( move = ttt_ai_block3(piecemask_self,piecemask_free) )) + return ttt_ai_piece_flag2pos(move); // place winning move + + dprint("TTT AI: checking opponent's winning move\n"); + if (( move = ttt_ai_block3(piecemask_opponent,piecemask_free) )) + return ttt_ai_piece_flag2pos(move); // block opponent + + dprint("TTT AI: random move\n"); + return ttt_ai_piece_flag2pos(ttt_ai_random(piecemask_free)); +} + +// AI move (if it's AI's turn) +void ttt_aimove(entity minigame) +{ + if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame.ttt_ai) ) + { + entity aiplayer = world; + while ( ( aiplayer = findentity(aiplayer,owner,minigame) ) ) + if ( aiplayer.classname == "minigame_player" && !aiplayer.minigame_playerslot ) + break; + + /* + * Build bit masks for the board pieces + * .---.---.---. + * | 4 | 32|256| 3 + * |---+---+---| + * | 2 | 16|128| 2 + * |---+---+---| + * | 1 | 8 | 64| 1 + * '---'---'---' + * A B C + */ + float piecemask_self = 0; + float piecemask_opponent = 0; + float piecemask_free = 0; + float pieceflag = 1; + string pos; + + float i,j; + for ( i = 0; i < 3; i++ ) + for ( j = 0; j < 3; j++ ) + { + pos = minigame_tile_buildname(i,j); + entity piece = ttt_find_piece(minigame,pos); + if ( piece ) + { + if ( piece.team == aiplayer.team ) + piecemask_self |= pieceflag; + else + piecemask_opponent |= pieceflag; + } + else + piecemask_free |= pieceflag; + pieceflag <<= 1; + } + + // TODO multiple AI difficulties + dprint(sprintf("TTT AI: self: %x opponent: %x free: %x\n", + piecemask_self, piecemask_opponent, piecemask_free)); + pos = ttt_ai_choose_simple(piecemask_self, piecemask_opponent, piecemask_free); + dprint("TTT AI: chosen move: ",pos,"\n\n"); + if ( !pos ) + dprint("Tic Tac Toe AI has derped!\n"); + else + ttt_move(minigame,aiplayer,pos); + } + minigame.message = ttt_turn_to_string(minigame.minigame_flags); +} + +// Make the correct move +void ttt_make_move(entity minigame) +{ + if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame_self.team) ) + { + if ( minigame.ttt_ai ) + { + ttt_move(minigame, minigame_self, ttt_curr_pos ); + ttt_aimove(minigame); + } + else + minigame_cmd("move ",ttt_curr_pos); + } +} + +void ttt_set_curr_pos(string s) +{ + if ( ttt_curr_pos ) + strunzone(ttt_curr_pos); + if ( s ) + s = strzone(s); + ttt_curr_pos = s; +} + +// Required function, handle client events +float minigame_event_ttt(entity minigame, string event, ...) +{ + switch(event) + { + case "activate": + { + ttt_set_curr_pos(""); + minigame.message = ttt_turn_to_string(minigame.minigame_flags); + return false; + } + case "key_pressed": + { + if((minigame.minigame_flags & TTT_TURN_TEAM) == minigame_self.team) + { + switch ( ...(0,float) ) + { + case K_RIGHTARROW: + case K_KP_RIGHTARROW: + if ( ! ttt_curr_pos ) + ttt_set_curr_pos("a3"); + else + ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,1,0,3,3)); + return true; + case K_LEFTARROW: + case K_KP_LEFTARROW: + if ( ! ttt_curr_pos ) + ttt_set_curr_pos("c3"); + else + ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,-1,0,3,3)); + return true; + case K_UPARROW: + case K_KP_UPARROW: + if ( ! ttt_curr_pos ) + ttt_set_curr_pos("a1"); + else + ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,0,1,3,3)); + return true; + case K_DOWNARROW: + case K_KP_DOWNARROW: + if ( ! ttt_curr_pos ) + ttt_set_curr_pos("a3"); + else + ttt_set_curr_pos(minigame_relative_tile(ttt_curr_pos,0,-1,3,3)); + return true; + case K_ENTER: + case K_KP_ENTER: + case K_SPACE: + ttt_make_move(minigame); + return true; + } + } + + return false; + } + case "mouse_pressed": + { + if(...(0,float) == K_MOUSE1) + { + ttt_make_move(minigame); + return true; + } + + return false; + } + case "mouse_moved": + { + vector mouse_pos = minigame_hud_normalize(mousepos,ttt_boardpos,ttt_boardsize); + if ( minigame.minigame_flags == (TTT_TURN_PLACE|minigame_self.team) ) + ttt_set_curr_pos(minigame_tile_name(mouse_pos,3,3)); + if ( ! ttt_valid_tile(ttt_curr_pos) ) + ttt_set_curr_pos(""); + + return true; + } + case "network_receive": + { + entity sent = ...(0,entity); + float sf = ...(1,float); + if ( sent.classname == "minigame" ) + { + if ( sf & MINIG_SF_UPDATE ) + { + sent.message = ttt_turn_to_string(sent.minigame_flags); + if ( sent.minigame_flags & minigame_self.team ) + minigame_prompt(); + } + + if ( (sf & TTT_SF_SINGLEPLAYER) ) + { + float ai = ReadByte(); + float spawnai = ai && !sent.ttt_ai; + sent.ttt_ai = ai; + + if ( spawnai ) + { + entity aiplayer = spawn(); + aiplayer.classname = "minigame_player"; + aiplayer.owner = minigame; + aiplayer.team = ai; + aiplayer.minigame_playerslot = 0; + aiplayer.minigame_autoclean = 1; + ttt_aimove(minigame); + } + + } + } + else if ( sent.classname == "minigame_player" && (sf & TTT_SF_PLAYERSCORE ) ) + { + sent.minigame_flags = ReadByte(); + } + + return false; + } + case "menu_show": + { + HUD_MinigameMenu_CustomEntry(...(0,entity),_("Next Match"),"next"); + HUD_MinigameMenu_CustomEntry(...(0,entity),_("Single Player"),"singleplayer"); + return false; + } + case "menu_click": + { + if(...(0,string) == "next") + { + if ( minigame.ttt_ai ) + { + ttt_next_match(minigame,minigame_self); + ttt_aimove(minigame); + } + else + minigame_cmd("next"); + } + else if ( ...(0,string) == "singleplayer" && !minigame.ttt_ai ) + { + if ( minigame_count_players(minigame) == 1 ) + minigame_cmd("singleplayer"); + } + return false; + } + } + + return false; +} + +#endif \ No newline at end of file diff --git a/qcsrc/common/minigames/minigames.qc b/qcsrc/common/minigames/minigames.qc new file mode 100644 index 000000000..46f0a9c57 --- /dev/null +++ b/qcsrc/common/minigames/minigames.qc @@ -0,0 +1,131 @@ +#include "minigames.qh" + +entity minigame_get_descriptor(string id) +{ + entity e; + for ( e = minigame_descriptors; e != world; e = e.list_next ) + if ( e.netname == id ) + return e; + return world; +} + +// Get letter index of a tile name +float minigame_tile_letter(string id) +{ + return str2chr(substring(id,0,1),0)-'a'; +} + +// Get number index of a tile name +// Note: this is 0 based, useful for mathematical operations +// Note: Since the tile notation starts from the bottom left, +// you may want to do number_of_rows - what_this_function_returns or something +float minigame_tile_number(string id) +{ + return stof(substring(id,1,-1)) -1 ; +} + +// Get relative position of the center of a given tile +vector minigame_tile_pos(string id, float rows, float columns) +{ + return eX*(minigame_tile_letter(id)+0.5)/columns + + eY - eY*(minigame_tile_number(id)+0.5)/rows; +} + +// Get a tile name from indices +string minigame_tile_buildname(float letter, float number) +{ + return strcat(chr2str('a'+letter),ftos(number+1)); +} + +// Get the id of a tile relative to the given one +string minigame_relative_tile(string start_id, float dx, float dy, float rows, float columns) +{ + float letter = minigame_tile_letter(start_id); + float number = minigame_tile_number(start_id); + letter = (letter+dx) % columns; + number = (number+dy) % rows; + if ( letter < 0 ) + letter = columns + letter; + if ( number < 0 ) + number = rows + number; + return minigame_tile_buildname(letter, number); +} + +// Get tile name from a relative position (matches the tile covering a square area) +string minigame_tile_name(vector pos, float rows, float columns) +{ + if ( pos_x < 0 || pos_x > 1 || pos_y < 0 || pos_y > 1 ) + return ""; // no tile + + float letter = floor(pos_x * columns); + float number = floor((1-pos_y) * rows); + return minigame_tile_buildname(letter, number); +} + +// Get the next team number (note: team numbers are between 1 and n_teams, inclusive) +float minigame_next_team(float curr_team, float n_teams) +{ + return curr_team % n_teams + 1; +} + +// set send flags only when on server +// (for example in game logic which can be used both in client and server +void minigame_server_sendflags(entity ent, float mgflags) +{ + #ifdef SVQC + ent.SendFlags |= mgflags; + #endif +} + +// Spawn linked entity on the server or local entity on the client +// This entity will be removed automatically when the minigame ends +entity msle_spawn(entity minigame_session, string class_name) +{ + entity e = spawn(); + e.classname = class_name; + e.owner = minigame_session; + e.minigame_autoclean = 1; + #ifdef SVQC + e.customizeentityforclient = minigame_CheckSend; + Net_LinkEntity(e, false, 0, minigame_SendEntity); + #endif + return e; +} + +const float msle_base_id = 2; +float msle_id(string class_name) +{ + if ( class_name == "minigame" ) return 1; + if ( class_name == "minigame_player" ) return 2; + float i = msle_base_id; +#define MSLE(Name, Fields) i++; if ( class_name == #Name ) return i; + MINIGAME_SIMPLELINKED_ENTITIES +#undef MSLE + return 0; +} + +string msle_classname(float id) +{ + if ( id == 1 ) return "minigame"; + if ( id == 2 ) return "minigame_player"; + float i = msle_base_id; +#define MSLE(Name, Fields) i++; if ( id == i ) return #Name; + MINIGAME_SIMPLELINKED_ENTITIES +#undef MSLE + return ""; +} + +float minigame_count_players(entity minigame) +{ + float pl_num = 0; + entity e; +#ifdef SVQC + for(e = minigame.minigame_players; e; e = e.list_next) +#elif defined(CSQC) + e = world; + while( (e = findentity(e,owner,minigame)) ) + if ( e.classname == "minigame_player" ) +#endif + pl_num++; + return pl_num; +} \ No newline at end of file diff --git a/qcsrc/common/minigames/minigames.qh b/qcsrc/common/minigames/minigames.qh new file mode 100644 index 000000000..3b8bbafb4 --- /dev/null +++ b/qcsrc/common/minigames/minigames.qh @@ -0,0 +1,124 @@ +#ifndef MINIGAMES_H +#define MINIGAMES_H + +entity minigame_descriptors; + +// previous node in a doubly linked list +.entity list_prev; +// next node in a linked list +.entity list_next; + +entity minigame_get_descriptor(string id); + +// Get letter index of a tile name +float minigame_tile_letter(string id); + +// Get number index of a tile name +// Note: this is 0 based, useful for mathematical operations +// Note: Since the tile notation starts from the bottom left, +// you may want to do number_of_rows - what_this_function_returns or something +float minigame_tile_number(string id); + +// Get relative position of the center of a given tile +vector minigame_tile_pos(string id, float rows, float columns); + +// Get a tile name from indices +string minigame_tile_buildname(float letter, float number); + +// Get the id of a tile relative to the given one +string minigame_relative_tile(string start_id, float dx, float dy, float rows, float columns); + +// Get tile name from a relative position (matches the tile covering a square area) +string minigame_tile_name(vector pos, float rows, float columns); + +// Get the next team number (note: team numbers are between 1 and n_teams, inclusive) +float minigame_next_team(float curr_team, float n_teams); + +// set send flags only when on server +// (for example in game logic which can be used both in client and server +void minigame_server_sendflags(entity ent, float mgflags); + +// count the number of players in a minigame session +float minigame_count_players(entity minigame); + +/// For minigame sessions: minigame descriptor object +.entity descriptor; + +/// For minigame sessions/descriptors: execute the given event +/// Client events: +/// mouse_moved(vector mouse_pos) +/// return 1 to handle input, 0 to discard +/// mouse_pressed/released(float K_Keycode) +/// return 1 to handle input, 0 to discard +/// note: see dpdefs/keycodes.qc for values +/// key_pressed/released(float K_Keycode) +/// return 1 to handle input, 0 to discard +/// note: see dpdefs/keycodes.qc for values +/// activate() +/// executed when the minigame is activated for the current client +/// deactivate() +/// executed when the minigame is deactivated for the current client +/// network_receive(entity received,float flags) +/// executed each time a networked entity is received +/// note: when this is called self == ...(0,entity) +/// You can use the MINIG_SF_ constants to check the send flags +/// IMPORTANT: always read in client everything you send from the server! +/// menu_show(entity parent_menu_item) +/// executed when the Current Game menu is shown, used to add custom entries +/// Call HUD_MinigameMenu_CustomEntry to do so (pass ...(0,entity) as first argument) +/// menu_click(string arg) +/// executed when a custom menu entry is clicked +/// Server events: +/// start() +/// executed when the minigame session is starting +/// end() +/// executed when the minigame session is shutting down +/// join(entity player) +/// executed when a player wants to join the session +/// return the player team number to accept the new player, 0 to discard +/// part(entity player) +/// executed when a player is going to leave the session +/// network_send(entity sent,float flags) +/// executed each time a networked entity is sent +/// note: when this is called self == ...(0,entity) +/// You can use the MINIG_SF_ constants to check the send flags +/// IMPORTANT: always read in client everything you send from the server! +/// cmd(entity minigame_player, float argc, string command) +/// self = client entity triggering this +/// argv(n) = console token +/// argc: number of console tokens +/// command: full command string +/// triggered when a player does "cmd minigame ..." with some unrecognized command +/// return 1 if the minigame has handled the command +/// impulse(entity minigame_player,float impulse) +/// self = client entity triggering this +/// triggered when a player does "impulse ..." +/// return 1 if the minigame has handled the impulse +.float(entity,string,...) minigame_event; + +// For run-time gameplay entities: Whether to be removed when the game is deactivated +.float minigame_autoclean; + +// For run-time gameplay entities: some place to store flags safely +.float minigame_flags; + +// Send flags, set to .SendFlags on networked entities to send entity information +// Flag values for customized events must be powers of 2 in the range +// [MINIG_SF_CUSTOM, MINIG_SF_MAX] (inclusive) +const float MINIG_SF_CREATE = 0x01; // Create a new object +const float MINIG_SF_UPDATE = 0x02; // miscellaneous entity update +const float MINIG_SF_CUSTOM = 0x10; // a customized networked event +const float MINIG_SF_MAX = 0x80; // maximum flag value sent over the network +const float MINIG_SF_ALL = 0xff; // use to resend everything + + +// Spawn linked entity on the server or local entity on the client +// This entity will be removed automatically when the minigame ends +entity msle_spawn(entity minigame_session, string class_name); + +#include "minigame/all.qh" + +float msle_id(string class_name); +string msle_classname(float id); + +#endif diff --git a/qcsrc/common/minigames/sv_minigames.qc b/qcsrc/common/minigames/sv_minigames.qc new file mode 100644 index 000000000..1953e9674 --- /dev/null +++ b/qcsrc/common/minigames/sv_minigames.qc @@ -0,0 +1,430 @@ +#include "minigames.qh" + +void player_clear_minigame(entity player) +{ + player.active_minigame = world; + if ( IS_PLAYER(player) ) + player.movetype = MOVETYPE_WALK; + else + player.movetype = MOVETYPE_FLY_WORLDONLY; + player.team_forced = 0; +} + +void minigame_rmplayer(entity minigame_session, entity player) +{ + entity e; + entity p = minigame_session.minigame_players; + + if ( p.minigame_players == player ) + { + if ( p.list_next == world ) + { + end_minigame(minigame_session); + return; + } + minigame_session.minigame_event(minigame_session,"part",player); + GameLogEcho(strcat(":minigame:part:",minigame_session.netname,":", + ftos(num_for_edict(player)),":",player.netname)); + minigame_session.minigame_players = p.list_next; + remove ( p ); + player_clear_minigame(player); + } + else + { + for ( e = p.list_next; e != world; e = e.list_next ) + { + if ( e.minigame_players == player ) + { + minigame_session.minigame_event(minigame_session,"part",player); + GameLogEcho(strcat(":minigame:part:",minigame_session.netname,":", + ftos(num_for_edict(player)),":",player.netname)); + p.list_next = e.list_next; + remove(e); + player_clear_minigame(player); + return; + } + p = e; + } + } +} + + +#define FIELD(Flags, Type,Name) if ( sf & (Flags) ) Write##Type(MSG_ENTITY, self.Name); +#define WriteVector(to,Name) WriteCoord(to,Name##_x); WriteCoord(to,Name##_y); WriteCoord(to,Name##_z) +#define WriteVector2D(to,Name) WriteCoord(to,Name##_x); WriteCoord(to,Name##_y) +#define WriteFloat WriteCoord +#define MSLE(Name,Fields) \ + else if ( self.classname == #Name ) { \ + if ( sf & MINIG_SF_CREATE ) WriteString(MSG_ENTITY,self.owner.netname); \ + Fields } + +// Send an entity to a client +// only use on minigame entities or entities with a minigame owner +float minigame_SendEntity(entity to, float sf) +{ + WriteByte(MSG_ENTITY, ENT_CLIENT_MINIGAME); + WriteByte(MSG_ENTITY, sf); + + if ( sf & MINIG_SF_CREATE ) + { + WriteShort(MSG_ENTITY,msle_id(self.classname)); + WriteString(MSG_ENTITY,self.netname); + } + + entity minigame_ent = self.owner; + + if ( self.classname == "minigame" ) + { + minigame_ent = self; + + if ( sf & MINIG_SF_CREATE ) + WriteString(MSG_ENTITY,self.descriptor.netname); + + if ( sf & MINIG_SF_UPDATE ) + WriteLong(MSG_ENTITY,self.minigame_flags); + } + else if ( self.classname == "minigame_player" ) + { + if ( sf & MINIG_SF_CREATE ) + { + WriteString(MSG_ENTITY,self.owner.netname); + WriteLong(MSG_ENTITY,num_for_edict(self.minigame_players)); + } + if ( sf & MINIG_SF_UPDATE ) + WriteByte(MSG_ENTITY,self.team); + } + MINIGAME_SIMPLELINKED_ENTITIES + + minigame_ent.minigame_event(minigame_ent,"network_send",self,sf); + + return 1; + +} +#undef FIELD +#undef MSLE +#undef WriteFloat + +// Force resend all minigame entities +void minigame_resend(entity minigame) +{ + minigame.SendFlags = MINIG_SF_ALL; + entity e = world; + while (( e = findentity(e,owner,minigame) )) + { + e.SendFlags = MINIG_SF_ALL; + } +} + +float minigame_CheckSend() +{ + entity e; + for ( e = self.owner.minigame_players; e != world; e = e.list_next ) + if ( e.minigame_players == other ) + return 1; + return 0; +} + +float minigame_addplayer(entity minigame_session, entity player) +{ + if ( player.active_minigame ) + { + if ( player.active_minigame == minigame_session ) + return 0; + minigame_rmplayer(player.active_minigame,player); + } + + float mgteam = minigame_session.minigame_event(minigame_session,"join",player); + + if ( mgteam ) + { + entity player_pointer = spawn(); + player_pointer.classname = "minigame_player"; + player_pointer.owner = minigame_session; + player_pointer.minigame_players = player; + player_pointer.team = mgteam; + player_pointer.list_next = minigame_session.minigame_players; + minigame_session.minigame_players = player_pointer; + player.active_minigame = minigame_session; + player_pointer.customizeentityforclient = minigame_CheckSend; + Net_LinkEntity(player_pointer, false, 0, minigame_SendEntity); + + if ( !IS_OBSERVER(player) && autocvar_sv_minigames_observer ) + { + entity e = self; + self = player; + PutObserverInServer(); + self = e; + } + if ( autocvar_sv_minigames_observer == 2 ) + player.team_forced = -1; + + minigame_resend(minigame_session); + } + GameLogEcho(strcat(":minigame:join",(mgteam?"":"fail"),":",minigame_session.netname,":", + ftos(num_for_edict(player)),":",player.netname)); + + return mgteam; +} + +entity start_minigame(entity player, string minigame ) +{ + if ( !autocvar_sv_minigames || !IS_REAL_CLIENT(player) ) + return world; + + entity e = minigame_get_descriptor(minigame); + if ( e ) + { + entity minig = spawn(); + minig.classname = "minigame"; + minig.netname = strzone(strcat(e.netname,"_",ftos(num_for_edict(minig)))); + minig.descriptor = e; + minig.minigame_event = e.minigame_event; + minig.minigame_event(minig,"start"); + GameLogEcho(strcat(":minigame:start:",minig.netname)); + if ( ! minigame_addplayer(minig,player) ) + { + dprint("Minigame ",minig.netname," rejected the first player join!\n"); + end_minigame(minig); + return world; + } + Net_LinkEntity(minig, false, 0, minigame_SendEntity); + + if ( !minigame_sessions ) + minigame_sessions = minig; + else + { + minigame_sessions.owner = minig; + minig.list_next = minigame_sessions; + minigame_sessions = minig; + } + return minig; + } + + return world; +} + +entity join_minigame(entity player, string game_id ) +{ + if ( !autocvar_sv_minigames || !IS_REAL_CLIENT(player) ) + return world; + + entity minig; + for ( minig = minigame_sessions; minig != world; minig = minig.list_next ) + { + if ( minig.netname == game_id ) + if ( minigame_addplayer(minig,player) ) + return minig; + } + + return world; +} + +void part_minigame(entity player ) +{ + entity minig = player.active_minigame; + + if ( minig && minig.classname == "minigame" ) + minigame_rmplayer(minig,player); +} + +void end_minigame(entity minigame_session) +{ + if ( minigame_session.owner ) + minigame_session.owner.list_next = minigame_session.list_next; + else + minigame_sessions = minigame_session.list_next; + + minigame_session.minigame_event(minigame_session,"end"); + GameLogEcho(strcat(":minigame:end:",minigame_session.netname)); + + + entity e = world; + while( (e = findentity(e, owner, minigame_session)) ) + if ( e.minigame_autoclean ) + { + dprint("SV Auto-cleaned: ",ftos(num_for_edict(e)), " (",e.classname,")\n"); + remove(e); + } + + entity p; + for ( e = minigame_session.minigame_players; e != world; e = p ) + { + p = e.list_next; + player_clear_minigame(e.minigame_players); + remove(e); + } + + strunzone(minigame_session.netname); + remove(minigame_session); +} + +void end_minigames() +{ + while ( minigame_sessions ) + { + end_minigame(minigame_sessions); + } +} + +void initialize_minigames() +{ + entity last_minig = world; + entity minig; + #define MINIGAME(name,nicename) \ + minig = spawn(); \ + minig.classname = "minigame_descriptor"; \ + minig.netname = #name; \ + minig.message = nicename; \ + minig.minigame_event = minigame_event_##name; \ + if ( !last_minig ) minigame_descriptors = minig; \ + else last_minig.list_next = minig; \ + last_minig = minig; + + REGISTERED_MINIGAMES + + #undef MINIGAME +} + +string invite_minigame(entity inviter, entity player) +{ + if ( !inviter || !inviter.active_minigame ) + return "Invalid minigame"; + if ( !VerifyClientEntity(player, true, false) ) + return "Invalid player"; + if ( inviter == player ) + return "You can't invite yourself"; + if ( player.active_minigame == inviter.active_minigame ) + return strcat(player.netname," is already playing"); + + Send_Notification(NOTIF_ONE, player, MSG_INFO, INFO_MINIGAME_INVITE, + inviter.active_minigame.netname, inviter.netname ); + + GameLogEcho(strcat(":minigame:invite:",inviter.active_minigame.netname,":", + ftos(num_for_edict(player)),":",player.netname)); + + return ""; +} + +entity minigame_find_player(entity client) +{ + if ( ! client.active_minigame ) + return world; + entity e; + for ( e = client.active_minigame.minigame_players; e; e = e.list_next ) + if ( e.minigame_players == client ) + return e; + return world; +} + +float MinigameImpulse(float imp) +{ + entity e = minigame_find_player(self); + if ( imp && self.active_minigame && e ) + { + return self.active_minigame.minigame_event(self.active_minigame,"impulse",e,imp); + } + return 0; +} + + + +void ClientCommand_minigame(float request, float argc, string command) +{ + if ( !autocvar_sv_minigames ) + { + sprint(self,"Minigames are not enabled!\n"); + return; + } + + if (request == CMD_REQUEST_COMMAND ) + { + string minig_cmd = argv(1); + if ( minig_cmd == "create" && argc > 2 ) + { + entity minig = start_minigame(self, argv(2)); + if ( minig ) + sprint(self,"Created minigame session: ",minig.netname,"\n"); + else + sprint(self,"Cannot start minigame session!\n"); + return; + } + else if ( minig_cmd == "join" && argc > 2 ) + { + entity minig = join_minigame(self, argv(2)); + if ( minig ) + sprint(self,"Joined: ",minig.netname,"\n"); + else + { + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_JOIN_PREVENT_MINIGAME); + sprint(self,"Cannot join given minigame session!\n"); + } + return; + } + else if ( minig_cmd == "list" ) + { + entity e; + for ( e = minigame_descriptors; e != world; e = e.list_next ) + sprint(self,e.netname," (",e.message,") ","\n"); + return; + } + else if ( minig_cmd == "list-sessions" ) + { + entity e; + for ( e = minigame_sessions; e != world; e = e.list_next ) + sprint(self,e.netname,"\n"); + return; + } + else if ( minig_cmd == "end" || minig_cmd == "part" ) + { + if ( self.active_minigame ) + { + part_minigame(self); + sprint(self,"Left minigame session\n"); + } + else + sprint(self,"You aren't playing any minigame...\n"); + return; + } + else if ( minig_cmd == "invite" && argc > 2 ) + { + if ( self.active_minigame ) + { + entity client = GetIndexedEntity(argc, 2); + string error = invite_minigame(self,client); + if ( error == "" ) + { + sprint(self,"You have invited ",client.netname, + " to join your game of ", self.active_minigame.descriptor.message, "\n"); + } + else + sprint(self,"Could not invite: ", error, ".\n"); + } + else + sprint(self,"You aren't playing any minigame...\n"); + return; + } + else if ( self.active_minigame ) + { + entity e = minigame_find_player(self); + string subcommand = substring(command,argv_end_index(0),-1); + float arg_c = tokenize_console(subcommand); + if ( self.active_minigame.minigame_event(self.active_minigame,"cmd",e,arg_c,subcommand) ) + return; + + } + else sprint(self,strcat("Wrong command:^1 ",command,"\n")); + } + + sprint(self, "\nUsage:^3 cmd minigame create \n"); + sprint(self, " Start a new minigame session\n"); + sprint(self, "Usage:^3 cmd minigame join \n"); + sprint(self, " Join an exising minigame session\n"); + sprint(self, "Usage:^3 cmd minigame list\n"); + sprint(self, " List available minigames\n"); + sprint(self, "Usage:^3 cmd minigame list-sessions\n"); + sprint(self, " List available minigames sessions\n"); + sprint(self, "Usage:^3 cmd minigame part|end\n"); + sprint(self, " Leave the current minigame\n"); + sprint(self, "Usage:^3 cmd minigame invite \n"); + sprint(self, " Invite the given player to join you in a minigame\n"); +} \ No newline at end of file diff --git a/qcsrc/common/minigames/sv_minigames.qh b/qcsrc/common/minigames/sv_minigames.qh new file mode 100644 index 000000000..c9591bb13 --- /dev/null +++ b/qcsrc/common/minigames/sv_minigames.qh @@ -0,0 +1,54 @@ +#ifndef SV_MINIGAMES_H +#define SV_MINIGAMES_H + +/// Initialize the minigame system +void initialize_minigames(); + +/// Create a new minigame session +/// \return minigame session entity +entity start_minigame(entity player, string minigame ); + +/// Join an existing minigame session +/// \return minigame session entity +entity join_minigame(entity player, string game_id ); + +/// Invite a player to join in a minigame +/// \return Error string +string invite_minigame(entity inviter, entity player); + +// Part minigame session +void part_minigame(entity player); + +// Ends a minigame session +void end_minigame(entity minigame_session); + +// Ends all minigame sessions +void end_minigames(); + +// Only sends entities to players who joined the minigame +// Use on customizeentityforclient for gameplay entities +float minigame_CheckSend(); + +// Check for minigame impulses +float MinigameImpulse(float imp); + +// Parse a client command ( cmd minigame ... ) +void ClientCommand_minigame(float request, float argc, string command); + +// Find the minigame_player entity for the given client entity +entity minigame_find_player(entity client); + +/// For players: Minigame being played +.entity active_minigame; + +/// For minigame sessions: list of players +/// For minigame_player: client entity +.entity minigame_players; + +entity minigame_sessions; + +float minigame_SendEntity(entity to, float sf); + +var void remove(entity e); + +#endif diff --git a/qcsrc/common/notifications.qc b/qcsrc/common/notifications.qc index b48daec7b..4c2c30ebb 100644 --- a/qcsrc/common/notifications.qc +++ b/qcsrc/common/notifications.qc @@ -1562,6 +1562,14 @@ void Local_Notification(int net_type, int net_name, ...count) #ifdef CSQC if(notif.nent_icon != "") { + if ( notif.nent_iconargs != "" ) + { + notif.nent_icon = Local_Notification_sprintf( + notif.nent_icon,notif.nent_iconargs, + s1, s2, s3, s4, f1, f2, f3, f4); + // remove the newline added by Local_Notification_sprintf + notif.nent_icon = strzone(substring(notif.nent_icon,0,strlen(notif.nent_icon)-1)); + } Local_Notification_HUD_Notify_Push( notif.nent_icon, notif.nent_hudargs, diff --git a/qcsrc/common/notifications.qh b/qcsrc/common/notifications.qh index 1cb1adf51..0a81a2c65 100644 --- a/qcsrc/common/notifications.qh +++ b/qcsrc/common/notifications.qh @@ -499,6 +499,7 @@ void Send_Notification_WOCOVA( MSG_INFO_NOTIF(1, INFO_RACE_NEW_IMPROVED, 1, 3, "s1 race_col f1ord race_col f2race_time race_diff", "s1 f2race_time", "race_newtime", _("^BG%s^BG improved their %s%s^BG place record with %s%s %s"), "") \ MSG_INFO_NOTIF(1, INFO_RACE_NEW_MISSING_UID, 1, 1, "s1 f1race_time", "s1 f1race_time", "race_newfail", _("^BG%s^BG scored a new record with ^F2%s^BG, but unfortunately lacks a UID and will be lost."), "") \ MSG_INFO_NOTIF(1, INFO_RACE_NEW_SET, 1, 2, "s1 race_col f1ord race_col f2race_time", "s1 f2race_time", "race_newrecordserver", _("^BG%s^BG set the %s%s^BG place record with %s%s"), "") \ + MULTIICON_INFO(1, INFO_MINIGAME_INVITE, 2, 0, "s2 minigame1_name s1","s2", "minigame1_d", "minigames/%s/icon_notif",_("^F4You have been invited by ^BG%s^F4 to join their game of ^F2%s^F4 (^F1%s^F4)"), "") \ MULTITEAM_INFO(1, INFO_SCORES_, 4, 0, 0, "", "", "", _("^TC^TT ^BGteam scores!"), "") \ MSG_INFO_NOTIF(1, INFO_SPECTATE_WARNING, 0, 1, "f1secs", "", "", _("^F2You have to become a player within the next %s, otherwise you will be kicked, because spectating isn't allowed at this time!"), "") \ MSG_INFO_NOTIF(1, INFO_SUPERWEAPON_PICKUP, 1, 0, "s1", "s1", "superweapons", _("^BG%s^K1 picked up a Superweapon"), "") \ @@ -734,7 +735,8 @@ void Send_Notification_WOCOVA( MSG_CENTER_NOTIF(1, CENTER_TEAMCHANGE_SUICIDE, 0, 1, "", CPID_TEAMCHANGE, "1 f1", _("^K1Suicide in ^COUNT"), "") \ MSG_CENTER_NOTIF(1, CENTER_TIMEOUT_BEGINNING, 0, 1, "", CPID_TIMEOUT, "1 f1", _("^F4Timeout begins in ^COUNT"), "") \ MSG_CENTER_NOTIF(1, CENTER_TIMEOUT_ENDING, 0, 1, "", CPID_TIMEOUT, "1 f1", _("^F4Timeout ends in ^COUNT"), "") \ - MSG_CENTER_NOTIF(1, CENTER_WEAPON_MINELAYER_LIMIT, 0, 1, "f1", NO_CPID, "0 0", _("^BGYou cannot place more than ^F2%s^BG mines at a time"), "") + MSG_CENTER_NOTIF(1, CENTER_WEAPON_MINELAYER_LIMIT, 0, 1, "f1", NO_CPID, "0 0", _("^BGYou cannot place more than ^F2%s^BG mines at a time"), "") \ + MSG_CENTER_NOTIF(1, CENTER_JOIN_PREVENT_MINIGAME, 0, 0, "", NO_CPID, "0 0", _("^K1Cannot join given minigame session!"), "" ) #define MULTITEAM_MULTI2(default,prefix,anncepre,infopre,centerpre) \ MSG_MULTI_NOTIF(default, prefix##RED, anncepre##RED, infopre##RED, centerpre##RED) \ @@ -1017,6 +1019,8 @@ float autocvar_notification_show_sprees_center_specialonly = true; item_centime: amount of time to display weapon message in centerprint item_buffname: return full name of a buff from buffid death_team: show the full name of the team a player is switching from + minigame1_name: return human readable name of a minigame from its id(s1) + minigame1_d: return descriptor name of a minigame from its id(s1) */ const float NOTIF_MAX_ARGS = 7; @@ -1072,7 +1076,9 @@ const float ARG_DC = 6; // unique result to durcnt/centerprint ARG_CASE(ARG_CS_SV, "item_wepammo", (s1 != "" ? sprintf(_(" with %s"), s1) : "")) \ ARG_CASE(ARG_DC, "item_centime", ftos(autocvar_notification_item_centerprinttime)) \ ARG_CASE(ARG_SV, "death_team", Team_ColoredFullName(f1)) \ - ARG_CASE(ARG_CS, "death_team", Team_ColoredFullName(f1 - 1)) + ARG_CASE(ARG_CS, "death_team", Team_ColoredFullName(f1 - 1)) \ + ARG_CASE(ARG_CS_SV_HA, "minigame1_name",find(world,netname,s1).descriptor.message) \ + ARG_CASE(ARG_CS_SV_HA, "minigame1_d", find(world,netname,s1).descriptor.netname) #define NOTIF_HIT_MAX(count,funcname) do { \ if(sel_num == count) { backtrace(sprintf("%s: Hit maximum arguments!\n", funcname)); break; } \ @@ -1464,6 +1470,50 @@ float notif_global_error; } \ ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name); +.string nent_iconargs; +#define MULTIICON_INFO(default,name,strnum,flnum,args,hudargs,iconargs,icon,normal,gentle) \ + NOTIF_ADD_AUTOCVAR(name, default) \ + float name; \ + void RegisterNotification_##name() \ + { \ + SET_FIELD_COUNT(name, NOTIF_FIRST, NOTIF_INFO_COUNT) \ + CHECK_MAX_COUNT(name, NOTIF_INFO_MAX, NOTIF_INFO_COUNT, "MSG_INFO") \ + Create_Notification_Entity( \ + /* COMMON ======================== */ \ + default, /* var_default */ \ + ACVNN(name), /* var_cvar */ \ + MSG_INFO, /* typeid */ \ + name, /* nameid */ \ + strtoupper(#name), /* namestring */ \ + strnum, /* strnum */ \ + flnum, /* flnum */ \ + /* ANNCE =========== */ \ + NO_MSG, /* channel */ \ + "", /* snd */ \ + NO_MSG, /* vol */ \ + NO_MSG, /* position */ \ + /* INFO & CENTER === */ \ + args, /* args */ \ + hudargs, /* hudargs */ \ + icon, /* icon */ \ + NO_MSG, /* cpid */ \ + "", /* durcnt */ \ + normal, /* normal */ \ + gentle, /* gentle */ \ + /* MULTI ============= */ \ + NO_MSG, /* anncename */ \ + NO_MSG, /* infoname */ \ + NO_MSG, /* centername */ \ + /* CHOICE ============== */ \ + NO_MSG, /* challow_def */ \ + NO_MSG, /* challow_var */ \ + NO_MSG, /* chtype */ \ + NO_MSG, /* optiona */ \ + NO_MSG); /* optionb */ \ + msg_info_notifs[name - 1].nent_iconargs = iconargs; \ + } \ + ACCUMULATE_FUNCTION(RegisterNotifications, RegisterNotification_##name); + #define MSG_CENTER_NOTIF(default,name,strnum,flnum,args,cpid,durcnt,normal,gentle) \ NOTIF_ADD_AUTOCVAR(name, default) \ float name; \ diff --git a/qcsrc/dpdefs/csprogsdefs.qh b/qcsrc/dpdefs/csprogsdefs.qh index 6f820c729..79b3e9b35 100644 --- a/qcsrc/dpdefs/csprogsdefs.qh +++ b/qcsrc/dpdefs/csprogsdefs.qh @@ -421,6 +421,7 @@ float( float b, ... ) max = #95; float(float minimum, float val, float maximum) bound = #96; float(float f, float f) pow = #97; entity(entity start, .float fld, float match) findfloat = #98; +entity(entity start, .entity fld, entity match) findentity = #98; float(string s) checkextension = #99; // FrikaC and Telejano range #100-#199 diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index 419956f86..dc6a5f581 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -881,4 +881,6 @@ float autocvar_g_buffs_vampire_damage_steal; float autocvar_g_buffs_invisible_alpha; float autocvar_g_buffs_flight_gravity; float autocvar_g_buffs_jump_height; +bool autocvar_sv_minigames; +bool autocvar_sv_minigames_observer; #endif diff --git a/qcsrc/server/cl_client.qc b/qcsrc/server/cl_client.qc index 9727cb947..e2d156195 100644 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@ -19,6 +19,8 @@ #include "../common/net_notice.qh" +#include "../common/minigames/sv_minigames.qh" + #include "../common/monsters/sv_monsters.qh" #include "../warpzonelib/server.qh" @@ -1273,6 +1275,9 @@ void ClientDisconnect (void) PlayerStats_GameReport_FinalizePlayer(self); + if ( self.active_minigame ) + part_minigame(self); + if(IS_PLAYER(self)) { pointparticles(particleeffectnum("spawn_event_neutral"), self.origin, '0 0 0', 1); } CheatShutdownClient(); @@ -1340,6 +1345,20 @@ void ClientDisconnect (void) } .float BUTTON_CHAT; +float ChatBubbleCustomize() +{ + entity e = WaypointSprite_getviewentity(other), own = self.owner; + + if(!own.deadflag && IS_PLAYER(own)) + { + if(own.BUTTON_CHAT) { self.skin = 0; return true; } + if(own.active_minigame) { self.skin = 1; return true; } + if(SAME_TEAM(own, e) && e != own) { self.skin = 2; return true; } + } + + return false; +} + void ChatBubbleThink() { self.nextthink = time; @@ -1350,10 +1369,6 @@ void ChatBubbleThink() remove(self); return; } - if (self.owner.BUTTON_CHAT && !self.owner.deadflag) - self.model = self.mdl; - else - self.model = ""; } void UpdateChatBubble() @@ -1366,6 +1381,8 @@ void UpdateChatBubble() self.chatbubbleentity = spawn(); self.chatbubbleentity.owner = self; self.chatbubbleentity.exteriormodeltoclient = self; + self.chatbubbleentity.alpha = 1; + self.chatbubbleentity.customizeentityforclient = ChatBubbleCustomize; self.chatbubbleentity.think = ChatBubbleThink; self.chatbubbleentity.nextthink = time; setmodel(self.chatbubbleentity, "models/misc/chatbubble.spr"); // precision set below @@ -1373,7 +1390,7 @@ void UpdateChatBubble() setorigin(self.chatbubbleentity, '0 0 15' + self.maxs.z * '0 0 1'); setattachment(self.chatbubbleentity, self, ""); // sticks to moving player better, also conserves bandwidth self.chatbubbleentity.mdl = self.chatbubbleentity.model; - self.chatbubbleentity.model = ""; + //self.chatbubbleentity.model = ""; self.chatbubbleentity.effects = EF_LOWPRECISION; } } @@ -2095,6 +2112,11 @@ void PrintWelcomeMessage() void ObserverThink() { + if ( self.impulse ) + { + MinigameImpulse(self.impulse); + self.impulse = 0; + } float prefered_movetype; if (self.flags & FL_JUMPRELEASED) { if (self.BUTTON_JUMP && !self.version_mismatch) { @@ -2125,6 +2147,11 @@ void ObserverThink() void SpectatorThink() { + if ( self.impulse ) + { + MinigameImpulse(self.impulse); + self.impulse = 0; + } if (self.flags & FL_JUMPRELEASED) { if (self.BUTTON_JUMP && !self.version_mismatch) { self.flags &= ~FL_JUMPRELEASED; diff --git a/qcsrc/server/cl_impulse.qc b/qcsrc/server/cl_impulse.qc index 79de1d275..350fcf6f4 100644 --- a/qcsrc/server/cl_impulse.qc +++ b/qcsrc/server/cl_impulse.qc @@ -4,6 +4,8 @@ #include "weapons/throwing.qh" +#include "../common/minigames/sv_minigames.qh" + #include "../common/weapons/weapons.qh" /* @@ -54,6 +56,10 @@ void ImpulseCommands (void) return; self.impulse = 0; + if ( self.active_minigame ) + if ( MinigameImpulse(imp) ) + return; + // allow only weapon change impulses when not in round time if(round_handler_IsActive() && !round_handler_IsRoundStarted()) if(imp == 17 || (imp >= 20 && imp < 200) || imp > 253) diff --git a/qcsrc/server/cl_player.qc b/qcsrc/server/cl_player.qc index 49d1c1b46..2afadfaac 100644 --- a/qcsrc/server/cl_player.qc +++ b/qcsrc/server/cl_player.qc @@ -3,6 +3,8 @@ #include "g_violence.qh" #include "miscfunctions.qh" +#include "../common/minigames/sv_minigames.qh" + #include "weapons/weaponstats.qh" void CopyBody_Think(void) @@ -860,6 +862,15 @@ float Say(entity source, float teamsay, entity privatesay, string msgin, float f if(cmsgstr != "") centerprint(privatesay, cmsgstr); } + else if ( teamsay && source.active_minigame ) + { + sprint(source, sourcemsgstr); + dedicated_print(msgstr); // send to server console too + FOR_EACH_REALCLIENT(head) + if(head != source) + if(head.active_minigame == source.active_minigame) + sprint(head, msgstr); + } else if(teamsay > 0) // team message, only sent to team mates { sprint(source, sourcemsgstr); diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc index 4a8b59eba..7b74e48bf 100644 --- a/qcsrc/server/command/cmd.qc +++ b/qcsrc/server/command/cmd.qc @@ -772,6 +772,7 @@ void ClientCommand_(float request) CLIENT_COMMAND("suggestmap", ClientCommand_suggestmap(request, arguments), "Suggest a map to the mapvote at match end") \ CLIENT_COMMAND("tell", ClientCommand_tell(request, arguments, command), "Send a message directly to a player") \ CLIENT_COMMAND("voice", ClientCommand_voice(request, arguments, command), "Send voice message via sound") \ + CLIENT_COMMAND("minigame", ClientCommand_minigame(request, arguments, command), "Start a minigame") \ /* nothing */ void ClientCommand_macro_help() diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc index 0a2708340..7b427c456 100644 --- a/qcsrc/server/g_world.qc +++ b/qcsrc/server/g_world.qc @@ -606,6 +606,8 @@ void spawnfunc_worldspawn (void) CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes); CALL_ACCUMULATED_FUNCTION(RegisterBuffs); + initialize_minigames(); + ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid)); TemporaryDB = db_create(); diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index 211af8a74..821fab9df 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -94,6 +94,8 @@ weapons/weaponsystem.qc ../common/monsters/monsters.qc ../common/monsters/spawn.qc ../common/monsters/sv_monsters.qc +../common/minigames/minigames.qc +../common/minigames/sv_minigames.qc ../common/nades.qc ../common/net_notice.qc ../common/notifications.qc -- 2.39.2