]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/minigames_merge
authorMario <zacjardine@y7mail.com>
Mon, 24 Aug 2015 08:34:57 +0000 (18:34 +1000)
committerMario <zacjardine@y7mail.com>
Mon, 24 Aug 2015 08:34:57 +0000 (18:34 +1000)
# Conflicts:
# _hud_descriptions.cfg
# defaultXonotic.cfg
# qcsrc/client/hud.qh
# qcsrc/client/main.qc
# qcsrc/client/progs.src
# qcsrc/client/view.qc
# qcsrc/common/constants.qh
# qcsrc/common/notifications.qh
# qcsrc/server/autocvars.qh
# qcsrc/server/cl_client.qc
# qcsrc/server/progs.src

117 files changed:
_hud_descriptions.cfg
binds-xonotic.cfg
defaultXonotic.cfg
gfx/hud/default/minigames/c4/board.jpg [new file with mode: 0644]
gfx/hud/default/minigames/c4/board_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/c4/board_over.jpg [new file with mode: 0644]
gfx/hud/default/minigames/c4/board_over_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/c4/board_under.jpg [new file with mode: 0644]
gfx/hud/default/minigames/c4/board_under_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/c4/icon.jpg [new file with mode: 0644]
gfx/hud/default/minigames/c4/icon_notif.jpg [new file with mode: 0644]
gfx/hud/default/minigames/c4/icon_notif_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/c4/piece1.tga [new file with mode: 0644]
gfx/hud/default/minigames/c4/piece2.tga [new file with mode: 0644]
gfx/hud/default/minigames/c4/winglow.tga [new file with mode: 0644]
gfx/hud/default/minigames/nmm/board.tga [new file with mode: 0644]
gfx/hud/default/minigames/nmm/icon.tga [new file with mode: 0644]
gfx/hud/default/minigames/nmm/icon_notif.tga [new file with mode: 0644]
gfx/hud/default/minigames/nmm/piece1.tga [new file with mode: 0644]
gfx/hud/default/minigames/nmm/piece2.tga [new file with mode: 0644]
gfx/hud/default/minigames/nmm/tile_active.tga [new file with mode: 0644]
gfx/hud/default/minigames/nmm/tile_available.tga [new file with mode: 0644]
gfx/hud/default/minigames/nmm/tile_selected.tga [new file with mode: 0644]
gfx/hud/default/minigames/pong/ball-glow.tga [new file with mode: 0644]
gfx/hud/default/minigames/pong/ball.png [new file with mode: 0644]
gfx/hud/default/minigames/pong/board.tga [new file with mode: 0644]
gfx/hud/default/minigames/pong/icon.tga [new file with mode: 0644]
gfx/hud/default/minigames/pong/icon_notif.tga [new file with mode: 0644]
gfx/hud/default/minigames/pong/paddle-glow.tga [new file with mode: 0644]
gfx/hud/default/minigames/pong/paddle.tga [new file with mode: 0644]
gfx/hud/default/minigames/pp/board.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/board_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/icon.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/icon_notif.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/icon_notif_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/piece1.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/piece1_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/piece2.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/piece2_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/piece_current.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/piece_current_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/piece_selected.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/piece_selected_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/piece_taken.jpg [new file with mode: 0644]
gfx/hud/default/minigames/pp/piece_taken_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/ps/board.jpg [new file with mode: 0644]
gfx/hud/default/minigames/ps/board_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/ps/icon.jpg [new file with mode: 0644]
gfx/hud/default/minigames/ps/icon_notif.jpg [new file with mode: 0644]
gfx/hud/default/minigames/ps/icon_notif_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/ps/piece.tga [new file with mode: 0644]
gfx/hud/default/minigames/ps/tile_available.tga [new file with mode: 0644]
gfx/hud/default/minigames/ps/tile_selected.tga [new file with mode: 0644]
gfx/hud/default/minigames/qto/board.jpg [new file with mode: 0644]
gfx/hud/default/minigames/qto/board_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/qto/icon.jpg [new file with mode: 0644]
gfx/hud/default/minigames/qto/icon_notif.jpg [new file with mode: 0644]
gfx/hud/default/minigames/qto/icon_notif_alpha.jpg [new file with mode: 0644]
gfx/hud/default/minigames/qto/piece0.tga [new file with mode: 0644]
gfx/hud/default/minigames/qto/piece1.tga [new file with mode: 0644]
gfx/hud/default/minigames/ttt/board.tga [new file with mode: 0644]
gfx/hud/default/minigames/ttt/icon.tga [new file with mode: 0644]
gfx/hud/default/minigames/ttt/icon_notif.tga [new file with mode: 0644]
gfx/hud/default/minigames/ttt/piece1.tga [new file with mode: 0644]
gfx/hud/default/minigames/ttt/piece2.tga [new file with mode: 0644]
gfx/hud/default/minigames/ttt/winglow.tga [new file with mode: 0644]
hud_luma.cfg
hud_luminos.cfg
hud_luminos_minimal.cfg
hud_luminos_minimal_xhair.cfg
hud_luminos_old.cfg
hud_nexuiz.cfg
keybinds.txt
keybinds.txt.de
keybinds.txt.es
keybinds.txt.fr
keybinds.txt.hu
keybinds.txt.it
keybinds.txt.ru
minigames.cfg [new file with mode: 0644]
models/sprites/minigame_busy.iqm [new file with mode: 0644]
models/sprites/minigame_busy.iqm_0.skin [new file with mode: 0644]
models/sprites/minigame_busy.tga [new file with mode: 0644]
qcsrc/client/command/cl_cmd.qc
qcsrc/client/hud.qc
qcsrc/client/hud.qh
qcsrc/client/hud_config.qh
qcsrc/client/main.qc
qcsrc/client/progs.src
qcsrc/client/scoreboard.qc
qcsrc/client/view.qc
qcsrc/common/constants.qh
qcsrc/common/minigames/cl_minigames.qc [new file with mode: 0644]
qcsrc/common/minigames/cl_minigames.qh [new file with mode: 0644]
qcsrc/common/minigames/cl_minigames_hud.qc [new file with mode: 0644]
qcsrc/common/minigames/minigame/all.qh [new file with mode: 0644]
qcsrc/common/minigames/minigame/c4.qc [new file with mode: 0644]
qcsrc/common/minigames/minigame/nmm.qc [new file with mode: 0644]
qcsrc/common/minigames/minigame/pong.qc [new file with mode: 0644]
qcsrc/common/minigames/minigame/pp.qc [new file with mode: 0644]
qcsrc/common/minigames/minigame/ps.qc [new file with mode: 0644]
qcsrc/common/minigames/minigame/qto.qc [new file with mode: 0644]
qcsrc/common/minigames/minigame/ttt.qc [new file with mode: 0644]
qcsrc/common/minigames/minigames.qc [new file with mode: 0644]
qcsrc/common/minigames/minigames.qh [new file with mode: 0644]
qcsrc/common/minigames/sv_minigames.qc [new file with mode: 0644]
qcsrc/common/minigames/sv_minigames.qh [new file with mode: 0644]
qcsrc/common/notifications.qc
qcsrc/common/notifications.qh
qcsrc/server/autocvars.qh
qcsrc/server/cl_client.qc
qcsrc/server/cl_impulse.qc
qcsrc/server/cl_player.qc
qcsrc/server/command/cmd.qc
qcsrc/server/g_world.qc
qcsrc/server/progs.src
scripts/minigames.shader [new file with mode: 0644]

index 7b6f739753e8c06ace70dd2eb5d98aedf727f4aa..1de28cb9fddd12aec713359503d8127d760b903c 100644 (file)
@@ -301,6 +301,46 @@ seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "" "minimum factor t
 seta hud_panel_centerprint_fade_subsequent_minfontsize "" "minimum factor for the font size from the subsequent fading effects"
 seta hud_panel_centerprint_fade_minfontsize "" "minimum factor for the font size from the fading in/out effects"
 
+seta hud_panel_minigameboard "" "enable/disable this panel"
+seta hud_panel_minigameboard_pos "" "position of this panel"
+seta hud_panel_minigameboard_size "" "size of this panel"
+seta hud_panel_minigameboard_bg "" "if set to something else than \"\" = override default background"
+seta hud_panel_minigameboard_bg_color "" "if set to something else than \"\" = override default panel background color"
+seta hud_panel_minigameboard_bg_color_team "" "override panel color with team color in team based games"
+seta hud_panel_minigameboard_bg_alpha "" "if set to something else than \"\" = override default panel background alpha"
+seta hud_panel_minigameboard_bg_border "" "if set to something else than \"\" = override default size of border around the background"
+seta hud_panel_minigameboard_bg_padding "" "if set to something else than \"\" = override default padding of contents from border"
+
+seta hud_panel_minigamestatus "" "enable/disable this panel"
+seta hud_panel_minigamestatus_pos "" "position of this panel"
+seta hud_panel_minigamestatus_size "" "size of this panel"
+seta hud_panel_minigamestatus_bg "" "if set to something else than \"\" = override default background"
+seta hud_panel_minigamestatus_bg_color "" "if set to something else than \"\" = override default panel background color"
+seta hud_panel_minigamestatus_bg_color_team "" "override panel color with team color in team based games"
+seta hud_panel_minigamestatus_bg_alpha "" "if set to something else than \"\" = override default panel background alpha"
+seta hud_panel_minigamestatus_bg_border "" "if set to something else than \"\" = override default size of border around the background"
+seta hud_panel_minigamestatus_bg_padding "" "if set to something else than \"\" = override default padding of contents from border"
+
+seta hud_panel_minigamehelp "" "enable/disable this panel"
+seta hud_panel_minigamehelp_pos "" "position of this panel"
+seta hud_panel_minigamehelp_size "" "size of this panel"
+seta hud_panel_minigamehelp_bg "" "if set to something else than \"\" = override default background"
+seta hud_panel_minigamehelp_bg_color "" "if set to something else than \"\" = override default panel background color"
+seta hud_panel_minigamehelp_bg_color_team "" "override panel color with team color in team based games"
+seta hud_panel_minigamehelp_bg_alpha "" "if set to something else than \"\" = override default panel background alpha"
+seta hud_panel_minigamehelp_bg_border "" "if set to something else than \"\" = override default size of border around the background"
+seta hud_panel_minigamehelp_bg_padding "" "if set to something else than \"\" = override default padding of contents from border"
+
+seta hud_panel_minigamemenu "" "enable/disable this panel"
+seta hud_panel_minigamemenu_pos "" "position of this panel"
+seta hud_panel_minigamemenu_size "" "size of this panel"
+seta hud_panel_minigamemenu_bg "" "if set to something else than \"\" = override default background"
+seta hud_panel_minigamemenu_bg_color "" "if set to something else than \"\" = override default panel background color"
+seta hud_panel_minigamemenu_bg_color_team "" "override panel color with team color in team based games"
+seta hud_panel_minigamemenu_bg_alpha "" "if set to something else than \"\" = override default panel background alpha"
+seta hud_panel_minigamemenu_bg_border "" "if set to something else than \"\" = override default size of border around the background"
+seta hud_panel_minigamemenu_bg_padding "" "if set to something else than \"\" = override default padding of contents from border"
+
 seta hud_panel_mapvote "" "enable/disable this panel"
 seta hud_panel_mapvote_pos "" "position of this panel"
 seta hud_panel_mapvote_size "" "size of this panel"
index f48842f8cdfffdde1f15aeb3b8fa6045d756aca4..759c1c00e8a78088514df991625e3d1fa0eaf904 100644 (file)
@@ -5,6 +5,8 @@ bind f6 team_auto
 
 bind f7 menu_showsandboxtools
 
+bind f9 "cl_cmd hud minigame"
+
 // movement
 bind w +forward
 bind a +moveleft
@@ -56,6 +58,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
index 798da0913b92a318de37cea67cfacbaeddeb1f59..d31cd76e58d424a31e0d57c17a07fec8158b0136 100644 (file)
@@ -1101,7 +1101,6 @@ 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 _urllib_nextslot 0 "temp variable"
 set cl_warpzone_usetrace 1 "do not touch"
@@ -1394,6 +1393,7 @@ exec gamemodes.cfg
 exec mutators.cfg
 exec notifications.cfg
 exec monsters.cfg
+exec minigames.cfg
 exec physics.cfg
 
 // load console command aliases and settings
diff --git a/gfx/hud/default/minigames/c4/board.jpg b/gfx/hud/default/minigames/c4/board.jpg
new file mode 100644 (file)
index 0000000..8ab7a8d
Binary files /dev/null and b/gfx/hud/default/minigames/c4/board.jpg differ
diff --git a/gfx/hud/default/minigames/c4/board_alpha.jpg b/gfx/hud/default/minigames/c4/board_alpha.jpg
new file mode 100644 (file)
index 0000000..416a858
Binary files /dev/null and b/gfx/hud/default/minigames/c4/board_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/c4/board_over.jpg b/gfx/hud/default/minigames/c4/board_over.jpg
new file mode 100644 (file)
index 0000000..9025039
Binary files /dev/null and b/gfx/hud/default/minigames/c4/board_over.jpg differ
diff --git a/gfx/hud/default/minigames/c4/board_over_alpha.jpg b/gfx/hud/default/minigames/c4/board_over_alpha.jpg
new file mode 100644 (file)
index 0000000..4befedf
Binary files /dev/null and b/gfx/hud/default/minigames/c4/board_over_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/c4/board_under.jpg b/gfx/hud/default/minigames/c4/board_under.jpg
new file mode 100644 (file)
index 0000000..3f364b3
Binary files /dev/null and b/gfx/hud/default/minigames/c4/board_under.jpg differ
diff --git a/gfx/hud/default/minigames/c4/board_under_alpha.jpg b/gfx/hud/default/minigames/c4/board_under_alpha.jpg
new file mode 100644 (file)
index 0000000..22c9bd1
Binary files /dev/null and b/gfx/hud/default/minigames/c4/board_under_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/c4/icon.jpg b/gfx/hud/default/minigames/c4/icon.jpg
new file mode 100644 (file)
index 0000000..85cf11f
Binary files /dev/null and b/gfx/hud/default/minigames/c4/icon.jpg differ
diff --git a/gfx/hud/default/minigames/c4/icon_notif.jpg b/gfx/hud/default/minigames/c4/icon_notif.jpg
new file mode 100644 (file)
index 0000000..f280450
Binary files /dev/null and b/gfx/hud/default/minigames/c4/icon_notif.jpg differ
diff --git a/gfx/hud/default/minigames/c4/icon_notif_alpha.jpg b/gfx/hud/default/minigames/c4/icon_notif_alpha.jpg
new file mode 100644 (file)
index 0000000..040990f
Binary files /dev/null and b/gfx/hud/default/minigames/c4/icon_notif_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/c4/piece1.tga b/gfx/hud/default/minigames/c4/piece1.tga
new file mode 100644 (file)
index 0000000..2b09198
Binary files /dev/null and b/gfx/hud/default/minigames/c4/piece1.tga differ
diff --git a/gfx/hud/default/minigames/c4/piece2.tga b/gfx/hud/default/minigames/c4/piece2.tga
new file mode 100644 (file)
index 0000000..fe0355c
Binary files /dev/null and b/gfx/hud/default/minigames/c4/piece2.tga differ
diff --git a/gfx/hud/default/minigames/c4/winglow.tga b/gfx/hud/default/minigames/c4/winglow.tga
new file mode 100644 (file)
index 0000000..1c91c0d
Binary files /dev/null and b/gfx/hud/default/minigames/c4/winglow.tga differ
diff --git a/gfx/hud/default/minigames/nmm/board.tga b/gfx/hud/default/minigames/nmm/board.tga
new file mode 100644 (file)
index 0000000..e253042
Binary files /dev/null and b/gfx/hud/default/minigames/nmm/board.tga differ
diff --git a/gfx/hud/default/minigames/nmm/icon.tga b/gfx/hud/default/minigames/nmm/icon.tga
new file mode 100644 (file)
index 0000000..8ccf34d
Binary files /dev/null and b/gfx/hud/default/minigames/nmm/icon.tga differ
diff --git a/gfx/hud/default/minigames/nmm/icon_notif.tga b/gfx/hud/default/minigames/nmm/icon_notif.tga
new file mode 100644 (file)
index 0000000..1c40a50
Binary files /dev/null and b/gfx/hud/default/minigames/nmm/icon_notif.tga differ
diff --git a/gfx/hud/default/minigames/nmm/piece1.tga b/gfx/hud/default/minigames/nmm/piece1.tga
new file mode 100644 (file)
index 0000000..2b09198
Binary files /dev/null and b/gfx/hud/default/minigames/nmm/piece1.tga differ
diff --git a/gfx/hud/default/minigames/nmm/piece2.tga b/gfx/hud/default/minigames/nmm/piece2.tga
new file mode 100644 (file)
index 0000000..fe0355c
Binary files /dev/null and b/gfx/hud/default/minigames/nmm/piece2.tga differ
diff --git a/gfx/hud/default/minigames/nmm/tile_active.tga b/gfx/hud/default/minigames/nmm/tile_active.tga
new file mode 100644 (file)
index 0000000..8cf312c
Binary files /dev/null and b/gfx/hud/default/minigames/nmm/tile_active.tga differ
diff --git a/gfx/hud/default/minigames/nmm/tile_available.tga b/gfx/hud/default/minigames/nmm/tile_available.tga
new file mode 100644 (file)
index 0000000..d10feee
Binary files /dev/null and b/gfx/hud/default/minigames/nmm/tile_available.tga differ
diff --git a/gfx/hud/default/minigames/nmm/tile_selected.tga b/gfx/hud/default/minigames/nmm/tile_selected.tga
new file mode 100644 (file)
index 0000000..d2e0731
Binary files /dev/null and b/gfx/hud/default/minigames/nmm/tile_selected.tga differ
diff --git a/gfx/hud/default/minigames/pong/ball-glow.tga b/gfx/hud/default/minigames/pong/ball-glow.tga
new file mode 100644 (file)
index 0000000..738d48a
Binary files /dev/null and b/gfx/hud/default/minigames/pong/ball-glow.tga differ
diff --git a/gfx/hud/default/minigames/pong/ball.png b/gfx/hud/default/minigames/pong/ball.png
new file mode 100644 (file)
index 0000000..23dc3c6
Binary files /dev/null and b/gfx/hud/default/minigames/pong/ball.png differ
diff --git a/gfx/hud/default/minigames/pong/board.tga b/gfx/hud/default/minigames/pong/board.tga
new file mode 100644 (file)
index 0000000..5d27455
Binary files /dev/null and b/gfx/hud/default/minigames/pong/board.tga differ
diff --git a/gfx/hud/default/minigames/pong/icon.tga b/gfx/hud/default/minigames/pong/icon.tga
new file mode 100644 (file)
index 0000000..fd1b78d
Binary files /dev/null and b/gfx/hud/default/minigames/pong/icon.tga differ
diff --git a/gfx/hud/default/minigames/pong/icon_notif.tga b/gfx/hud/default/minigames/pong/icon_notif.tga
new file mode 100644 (file)
index 0000000..f6e00c2
Binary files /dev/null and b/gfx/hud/default/minigames/pong/icon_notif.tga differ
diff --git a/gfx/hud/default/minigames/pong/paddle-glow.tga b/gfx/hud/default/minigames/pong/paddle-glow.tga
new file mode 100644 (file)
index 0000000..d620055
Binary files /dev/null and b/gfx/hud/default/minigames/pong/paddle-glow.tga differ
diff --git a/gfx/hud/default/minigames/pong/paddle.tga b/gfx/hud/default/minigames/pong/paddle.tga
new file mode 100644 (file)
index 0000000..06a76a6
Binary files /dev/null and b/gfx/hud/default/minigames/pong/paddle.tga differ
diff --git a/gfx/hud/default/minigames/pp/board.jpg b/gfx/hud/default/minigames/pp/board.jpg
new file mode 100644 (file)
index 0000000..b435e3d
Binary files /dev/null and b/gfx/hud/default/minigames/pp/board.jpg differ
diff --git a/gfx/hud/default/minigames/pp/board_alpha.jpg b/gfx/hud/default/minigames/pp/board_alpha.jpg
new file mode 100644 (file)
index 0000000..14938ed
Binary files /dev/null and b/gfx/hud/default/minigames/pp/board_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/pp/icon.jpg b/gfx/hud/default/minigames/pp/icon.jpg
new file mode 100644 (file)
index 0000000..7889f1b
Binary files /dev/null and b/gfx/hud/default/minigames/pp/icon.jpg differ
diff --git a/gfx/hud/default/minigames/pp/icon_notif.jpg b/gfx/hud/default/minigames/pp/icon_notif.jpg
new file mode 100644 (file)
index 0000000..b036d95
Binary files /dev/null and b/gfx/hud/default/minigames/pp/icon_notif.jpg differ
diff --git a/gfx/hud/default/minigames/pp/icon_notif_alpha.jpg b/gfx/hud/default/minigames/pp/icon_notif_alpha.jpg
new file mode 100644 (file)
index 0000000..040990f
Binary files /dev/null and b/gfx/hud/default/minigames/pp/icon_notif_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/pp/piece1.jpg b/gfx/hud/default/minigames/pp/piece1.jpg
new file mode 100644 (file)
index 0000000..9bb789d
Binary files /dev/null and b/gfx/hud/default/minigames/pp/piece1.jpg differ
diff --git a/gfx/hud/default/minigames/pp/piece1_alpha.jpg b/gfx/hud/default/minigames/pp/piece1_alpha.jpg
new file mode 100644 (file)
index 0000000..9bb789d
Binary files /dev/null and b/gfx/hud/default/minigames/pp/piece1_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/pp/piece2.jpg b/gfx/hud/default/minigames/pp/piece2.jpg
new file mode 100644 (file)
index 0000000..7701f42
Binary files /dev/null and b/gfx/hud/default/minigames/pp/piece2.jpg differ
diff --git a/gfx/hud/default/minigames/pp/piece2_alpha.jpg b/gfx/hud/default/minigames/pp/piece2_alpha.jpg
new file mode 100644 (file)
index 0000000..7701f42
Binary files /dev/null and b/gfx/hud/default/minigames/pp/piece2_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/pp/piece_current.jpg b/gfx/hud/default/minigames/pp/piece_current.jpg
new file mode 100644 (file)
index 0000000..02afe48
Binary files /dev/null and b/gfx/hud/default/minigames/pp/piece_current.jpg differ
diff --git a/gfx/hud/default/minigames/pp/piece_current_alpha.jpg b/gfx/hud/default/minigames/pp/piece_current_alpha.jpg
new file mode 100644 (file)
index 0000000..02afe48
Binary files /dev/null and b/gfx/hud/default/minigames/pp/piece_current_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/pp/piece_selected.jpg b/gfx/hud/default/minigames/pp/piece_selected.jpg
new file mode 100644 (file)
index 0000000..eaf9970
Binary files /dev/null and b/gfx/hud/default/minigames/pp/piece_selected.jpg differ
diff --git a/gfx/hud/default/minigames/pp/piece_selected_alpha.jpg b/gfx/hud/default/minigames/pp/piece_selected_alpha.jpg
new file mode 100644 (file)
index 0000000..71d65fd
Binary files /dev/null and b/gfx/hud/default/minigames/pp/piece_selected_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/pp/piece_taken.jpg b/gfx/hud/default/minigames/pp/piece_taken.jpg
new file mode 100644 (file)
index 0000000..cafa6c3
Binary files /dev/null and b/gfx/hud/default/minigames/pp/piece_taken.jpg differ
diff --git a/gfx/hud/default/minigames/pp/piece_taken_alpha.jpg b/gfx/hud/default/minigames/pp/piece_taken_alpha.jpg
new file mode 100644 (file)
index 0000000..cafa6c3
Binary files /dev/null and b/gfx/hud/default/minigames/pp/piece_taken_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/ps/board.jpg b/gfx/hud/default/minigames/ps/board.jpg
new file mode 100644 (file)
index 0000000..882e4d2
Binary files /dev/null and b/gfx/hud/default/minigames/ps/board.jpg differ
diff --git a/gfx/hud/default/minigames/ps/board_alpha.jpg b/gfx/hud/default/minigames/ps/board_alpha.jpg
new file mode 100644 (file)
index 0000000..43f18b4
Binary files /dev/null and b/gfx/hud/default/minigames/ps/board_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/ps/icon.jpg b/gfx/hud/default/minigames/ps/icon.jpg
new file mode 100644 (file)
index 0000000..1390c89
Binary files /dev/null and b/gfx/hud/default/minigames/ps/icon.jpg differ
diff --git a/gfx/hud/default/minigames/ps/icon_notif.jpg b/gfx/hud/default/minigames/ps/icon_notif.jpg
new file mode 100644 (file)
index 0000000..b9024ae
Binary files /dev/null and b/gfx/hud/default/minigames/ps/icon_notif.jpg differ
diff --git a/gfx/hud/default/minigames/ps/icon_notif_alpha.jpg b/gfx/hud/default/minigames/ps/icon_notif_alpha.jpg
new file mode 100644 (file)
index 0000000..040990f
Binary files /dev/null and b/gfx/hud/default/minigames/ps/icon_notif_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/ps/piece.tga b/gfx/hud/default/minigames/ps/piece.tga
new file mode 100644 (file)
index 0000000..2b09198
Binary files /dev/null and b/gfx/hud/default/minigames/ps/piece.tga differ
diff --git a/gfx/hud/default/minigames/ps/tile_available.tga b/gfx/hud/default/minigames/ps/tile_available.tga
new file mode 100644 (file)
index 0000000..d10feee
Binary files /dev/null and b/gfx/hud/default/minigames/ps/tile_available.tga differ
diff --git a/gfx/hud/default/minigames/ps/tile_selected.tga b/gfx/hud/default/minigames/ps/tile_selected.tga
new file mode 100644 (file)
index 0000000..d2e0731
Binary files /dev/null and b/gfx/hud/default/minigames/ps/tile_selected.tga differ
diff --git a/gfx/hud/default/minigames/qto/board.jpg b/gfx/hud/default/minigames/qto/board.jpg
new file mode 100644 (file)
index 0000000..cf8fe0a
Binary files /dev/null and b/gfx/hud/default/minigames/qto/board.jpg differ
diff --git a/gfx/hud/default/minigames/qto/board_alpha.jpg b/gfx/hud/default/minigames/qto/board_alpha.jpg
new file mode 100644 (file)
index 0000000..44a8708
Binary files /dev/null and b/gfx/hud/default/minigames/qto/board_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/qto/icon.jpg b/gfx/hud/default/minigames/qto/icon.jpg
new file mode 100644 (file)
index 0000000..a534f48
Binary files /dev/null and b/gfx/hud/default/minigames/qto/icon.jpg differ
diff --git a/gfx/hud/default/minigames/qto/icon_notif.jpg b/gfx/hud/default/minigames/qto/icon_notif.jpg
new file mode 100644 (file)
index 0000000..90e0e2e
Binary files /dev/null and b/gfx/hud/default/minigames/qto/icon_notif.jpg differ
diff --git a/gfx/hud/default/minigames/qto/icon_notif_alpha.jpg b/gfx/hud/default/minigames/qto/icon_notif_alpha.jpg
new file mode 100644 (file)
index 0000000..040990f
Binary files /dev/null and b/gfx/hud/default/minigames/qto/icon_notif_alpha.jpg differ
diff --git a/gfx/hud/default/minigames/qto/piece0.tga b/gfx/hud/default/minigames/qto/piece0.tga
new file mode 100644 (file)
index 0000000..fad7dbd
Binary files /dev/null and b/gfx/hud/default/minigames/qto/piece0.tga differ
diff --git a/gfx/hud/default/minigames/qto/piece1.tga b/gfx/hud/default/minigames/qto/piece1.tga
new file mode 100644 (file)
index 0000000..80a34c2
Binary files /dev/null and b/gfx/hud/default/minigames/qto/piece1.tga differ
diff --git a/gfx/hud/default/minigames/ttt/board.tga b/gfx/hud/default/minigames/ttt/board.tga
new file mode 100644 (file)
index 0000000..3889e28
Binary files /dev/null and b/gfx/hud/default/minigames/ttt/board.tga differ
diff --git a/gfx/hud/default/minigames/ttt/icon.tga b/gfx/hud/default/minigames/ttt/icon.tga
new file mode 100644 (file)
index 0000000..5cbc583
Binary files /dev/null and b/gfx/hud/default/minigames/ttt/icon.tga differ
diff --git a/gfx/hud/default/minigames/ttt/icon_notif.tga b/gfx/hud/default/minigames/ttt/icon_notif.tga
new file mode 100644 (file)
index 0000000..9054555
Binary files /dev/null and b/gfx/hud/default/minigames/ttt/icon_notif.tga differ
diff --git a/gfx/hud/default/minigames/ttt/piece1.tga b/gfx/hud/default/minigames/ttt/piece1.tga
new file mode 100644 (file)
index 0000000..ce7e2c6
Binary files /dev/null and b/gfx/hud/default/minigames/ttt/piece1.tga differ
diff --git a/gfx/hud/default/minigames/ttt/piece2.tga b/gfx/hud/default/minigames/ttt/piece2.tga
new file mode 100644 (file)
index 0000000..2e12b9e
Binary files /dev/null and b/gfx/hud/default/minigames/ttt/piece2.tga differ
diff --git a/gfx/hud/default/minigames/ttt/winglow.tga b/gfx/hud/default/minigames/ttt/winglow.tga
new file mode 100644 (file)
index 0000000..1c91c0d
Binary files /dev/null and b/gfx/hud/default/minigames/ttt/winglow.tga differ
index 573e8da309c8d201e77b16622f0077ab5feb7b3b..70d0764b67e25e28d419eacb8977f5b587bf10e7 100644 (file)
@@ -326,4 +326,44 @@ seta hud_panel_itemstime_text "1"
 seta hud_panel_itemstime_ratio "2"
 seta hud_panel_itemstime_dynamicsize "1"
 
+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
index 302d4d769fb6efff8c45f1f70836d559981929b0..a1509ae7bf7679fa349091f02613536f8fd7bd0a 100644 (file)
@@ -326,4 +326,44 @@ seta hud_panel_itemstime_text "1"
 seta hud_panel_itemstime_ratio "2"
 seta hud_panel_itemstime_dynamicsize "1"
 
+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
index 3386129fcfc92f49f41b2bb02a8a80b8a335c65b..e5aec23184124939077331665dde381e2c1dfe89 100644 (file)
@@ -326,4 +326,44 @@ seta hud_panel_itemstime_text "1"
 seta hud_panel_itemstime_ratio "2"
 seta hud_panel_itemstime_dynamicsize "1"
 
+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
index 200c763a4ab615a1cbda87c79e235bf549bfe0bf..f313b4c435530af0b6654c20c4ab16cadfabe2f5 100644 (file)
@@ -326,4 +326,44 @@ seta hud_panel_itemstime_text "1"
 seta hud_panel_itemstime_ratio "2"
 seta hud_panel_itemstime_dynamicsize "1"
 
+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
index 95aab98e1853890960aa542a3a867d0f6978a7d1..4d27482b09e81d8b33077b146c255c9bf6be25b0 100644 (file)
@@ -326,4 +326,44 @@ seta hud_panel_itemstime_text "1"
 seta hud_panel_itemstime_ratio "3.5"
 seta hud_panel_itemstime_dynamicsize "1"
 
+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
index f1e1cdf24e56fd08be3aa55c435bd5127450d04d..5eb75cfde006cc314a56fa2c19e3fe6d6f5f7e75 100644 (file)
@@ -326,4 +326,44 @@ seta hud_panel_itemstime_text "1"
 seta hud_panel_itemstime_ratio "2"
 seta hud_panel_itemstime_dynamicsize "1"
 
+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
index 189d02eb4ddf0135ab0ed9ededd38db602495949..9cfbccaa9d14891780ebb2c9de0f520205749745 100644 (file)
@@ -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"
index 0c2aaf2b18a85cf0d3385ef1e355c667c532869c..0f30ce2bf8d859dd6f4457e11ffd47f1384444c6 100644 (file)
@@ -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"
index 51d9bfc390374ef73cf0f145e93fc37097119e3a..79c3124936b6ee69c3a02b4b7a8fdc127ce4857a 100644 (file)
@@ -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"
index 15a21f01e6df33a4a60eb24c093c437f7c40729e..2f0ae6af3964af297b6908fce35d5e8d4071020d 100644 (file)
@@ -35,6 +35,7 @@
 "+showscores"                           "afficher les scores"
 "screenshot"                            "capture d'écran"
 "+hud_panel_radar_maximized"            "agrandir le radar"
+"cl_cmd hud minigame"                   "activer/désactiver le menu de mini-jeu"
 ""                                      ""
 ""                                      "Communication"
 "messagemode"                           "tchat public"
index 3ae11f77f76281a331557c0e3e82f45af21e304b..e22299a447babe9b1a131b54c6aa77b66c608782 100644 (file)
@@ -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"
index 40f921033cb0b84512eeca2bf9d10c027214161c..069f9dfbbbee2da3b2a505041d0c56232b8bc439 100644 (file)
@@ -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"
index 7ab93ff8ff7823cea577dd12a1600fa82d8292c2..7be181d0240b8b3f001918f63e9629d816a3d237 100644 (file)
@@ -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/minigames.cfg b/minigames.cfg
new file mode 100644 (file)
index 0000000..04c370c
--- /dev/null
@@ -0,0 +1,14 @@
+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"
+
+// Pong
+set sv_minigames_pong_paddle_size   0.3     "Paddle length relative to the board size"
+set sv_minigames_pong_paddle_speed  1       "Paddle speed (board size per second)"
+
+set sv_minigames_pong_ball_wait     1       "Number of seconds between reset and throw"
+set sv_minigames_pong_ball_speed    1.5     "Ball speed (board size per second)"
+set sv_minigames_pong_ball_radius   0.03125 "Ball radius relative to the board size"
+set sv_minigames_pong_ball_number   1       "Number of balls to be played at once"
+
+set sv_minigames_pong_ai_thinkspeed 0.1     "Seconds between AI actions"
+set sv_minigames_pong_ai_tolerance  0.33    "Distance of the ball relative to the paddle size"
\ No newline at end of file
diff --git a/models/sprites/minigame_busy.iqm b/models/sprites/minigame_busy.iqm
new file mode 100644 (file)
index 0000000..a5cc3e6
Binary files /dev/null and b/models/sprites/minigame_busy.iqm differ
diff --git a/models/sprites/minigame_busy.iqm_0.skin b/models/sprites/minigame_busy.iqm_0.skin
new file mode 100644 (file)
index 0000000..2fb54ec
--- /dev/null
@@ -0,0 +1 @@
+Plane,minigame_busy
\ No newline at end of file
diff --git a/models/sprites/minigame_busy.tga b/models/sprites/minigame_busy.tga
new file mode 100644 (file)
index 0000000..c896b2a
Binary files /dev/null and b/models/sprites/minigame_busy.tga differ
index e80130c596c20a22e6ac47c775229379f3391aa7..9fe33a4fc00cf06d88bc51a93c45d1a20c5f72b0 100644 (file)
@@ -254,6 +254,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))
@@ -305,7 +314,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;
                }
        }
index 3482a62bf06faf9cb266cccb9efcd55046463198..7125739cff922cec08dad3f759463ca119b4f9d7 100644 (file)
@@ -4814,12 +4814,30 @@ void HUD_CenterPrint (void)
 }
 
 
+// Minigame
+//
+#include "../common/minigames/cl_minigames_hud.qc"
+
 /*
 ==================
 Main HUD system
 ==================
 */
 
+bool HUD_Panel_CheckFlags(int 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
@@ -4845,12 +4863,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)
@@ -4949,14 +4972,14 @@ void HUD_Main (void)
        hud_draw_maximized = 0;
        // draw panels in the 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();
 
index ee8ca1c687a9377e931be117f4aae7679bbaf56d..9850b78d20b91696d465b69f4a92fe72f3c2d22b 100644 (file)
@@ -133,6 +133,13 @@ string GetPlayerName(int i);
 float stringwidth_nocolors(string s, vector theSize);
 void HUD_Panel_DrawProgressBar(vector theOrigin, vector theSize, string pic, float length_ratio, bool vertical, float baralign, vector theColor, float theAlpha, int drawflag);
 
+.int panel_showflags;
+const int PANEL_SHOW_NEVER    = 0x00;
+const int PANEL_SHOW_MAINGAME = 0x01;
+const int PANEL_SHOW_MINIGAME = 0x02;
+const int PANEL_SHOW_ALWAYS   = 0xff;
+bool HUD_Panel_CheckFlags(int showflags);
+
 
 // prev_* vars contain the health/armor at the previous FRAME
 // set to -1 when player is dead or was not playing
@@ -149,28 +156,31 @@ int prev_p_health, prev_p_armor;
 void HUD_ItemsTime();
 
 #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(MAPVOTE      , MapVote_Draw     , mapvote) \
-       HUD_PANEL(ITEMSTIME    , HUD_ItemsTime    , itemstime) \
-       // always add new panels to the end of list
-
-#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(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   ) \
+       HUD_PANEL(MAPVOTE      ,  MapVote_Draw      ,mapvote,       PANEL_SHOW_ALWAYS   ) \
+       HUD_PANEL(ITEMSTIME    ,  HUD_ItemsTime     ,itemstime,     PANEL_SHOW_MAINGAME )
+
+#define HUD_PANEL(NAME, draw_func, name, showflags)                                                                                                                                                    \
        int HUD_PANEL_##NAME;                                                                                                                                                                                   \
        void draw_func(void);                                                                                                                                                                                   \
        void RegisterHUD_Panel_##NAME() {                                                                                                                                                               \
@@ -180,7 +190,8 @@ void HUD_ItemsTime();
                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);
index 39dc6599181c7956c6a3a9f89f01d5bb73e01f8e..bac199750e11a40c94fa81b5aa704cfb23804007 100644 (file)
@@ -1,6 +1,14 @@
 #ifndef HUD_CONFIG_H
 #define HUD_CONFIG_H
 
+const int S_MOUSE1 = 1;
+const int S_MOUSE2 = 2;
+const int S_MOUSE3 = 4;
+int mouseClicked;
+int prevMouseClicked; // previous state
+float prevMouseClickedTime; // time during previous left mouse click, to check for doubleclicks
+vector prevMouseClickedPos; // pos during previous left mouse click, to check for doubleclicks
+
 void HUD_Panel_ExportCfg(string cfgname);
 
 void HUD_Panel_Mouse();
@@ -11,12 +19,4 @@ void HUD_Configure_PostDraw();
 
 float HUD_Panel_InputEvent(float bInputType, float nPrimary, float nSecondary);
 
-const int S_MOUSE1 = 1;
-const int S_MOUSE2 = 2;
-const int S_MOUSE3 = 4;
-int mouseClicked;
-int prevMouseClicked; // previous state
-float prevMouseClickedTime; // time during previous left mouse click, to check for doubleclicks
-vector prevMouseClickedPos; // pos during previous left mouse click, to check for doubleclicks
-
 #endif
index e39b3346a354ec5fc5b7f1d80a6c3cc9286f1670..fac620461785a032d241cfe720fbb8e631c4ee2b 100644 (file)
@@ -155,6 +155,8 @@ void CSQC_Init(void)
        CALL_ACCUMULATED_FUNCTION(RegisterHUD_Panels);
        CALL_ACCUMULATED_FUNCTION(RegisterEffects);
 
+       initialize_minigames();
+
        WaypointSprite_Load();
 
        // precaches
@@ -229,6 +231,9 @@ void Shutdown(void)
                if (!(calledhooks & HOOK_END))
                        localcmd("\ncl_hook_gameend\n");
        }
+
+       deactivate_minigame();
+       HUD_MinigameMenu_Close();
 }
 
 .float has_team;
@@ -378,6 +383,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;
@@ -891,6 +899,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;
                case ENT_CLIENT_VIEWLOC: ent_viewloc(); break;
                case ENT_CLIENT_VIEWLOC_TRIGGER: ent_viewloc_trigger(); break;
                case ENT_CLIENT_LADDER: ent_func_ladder(); break;
index 9b687532ff43cb4a091d3eeb7cbc8010941f5dee..0c030de3335f84b3702f527b6d93e5808fda6ede 100644 (file)
@@ -57,6 +57,9 @@ weapons/projectile.qc // TODO
 
 ../common/viewloc.qc
 
+../common/minigames/minigames.qc
+../common/minigames/cl_minigames.qc
+
 ../common/items/all.qc
 ../common/monsters/all.qc
 ../common/mutators/all.qc
index 126cc904d02aab36c43c7f903e3a769f6b53d375..a24b9437902bd7659b20ff0c4833ce2a24ba41f8 100644 (file)
@@ -7,6 +7,7 @@
 #include "../common/constants.qh"
 #include "../common/counting.qh"
 #include "../common/mapinfo.qh"
+#include "../common/minigames/cl_minigames.qh"
 #include "../common/stats.qh"
 #include "../common/teams.qh"
 #include "../common/util.qh"
@@ -968,7 +969,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;
index e846a662b17996bd89b92854f6867aa22b22f240..533668297cbca345752c1f524282a8a008a84010 100644 (file)
@@ -562,7 +562,9 @@ void UpdateCrosshair()
                        CSQC_common_hud();
 
        // crosshair goes VERY LAST
-       if(!scoreboard_active && !camera_active && intermission != 2 && spectatee_status != -1 && hud == HUD_NORMAL && !csqcplayer.viewloc)
+       if(!scoreboard_active && !camera_active && intermission != 2 && 
+               spectatee_status != -1 && hud == HUD_NORMAL && !csqcplayer.viewloc &&
+               !HUD_MinigameMenu_IsOpened() )
        {
                if (!autocvar_crosshair_enabled) // main toggle for crosshair rendering
                        return;
@@ -1835,6 +1837,8 @@ void CSQC_UpdateView(float w, float h)
 
        if(autocvar__hud_configure)
                HUD_Panel_Mouse();
+       else if ( HUD_MinigameMenu_IsOpened() || minigame_isactive() )
+               HUD_Minigame_Mouse();
        else
                HUD_Radar_Mouse();
 
index 7de2a888322d72b6ac3eb693b7852ef0d5138607..bc18b4dc7ada43a12d8bb9741756f7ea784346cf 100644 (file)
@@ -125,9 +125,9 @@ const int ENT_CLIENT_KEYLOCK = 71;
 const int ENT_CLIENT_GENERATOR = 72;
 const int ENT_CLIENT_CONTROLPOINT_ICON = 73;
 const int ENT_CLIENT_EFFECT = 74;
+const int ENT_CLIENT_MINIGAME = 75;
 const int ENT_CLIENT_VIEWLOC = 78;
 const int ENT_CLIENT_VIEWLOC_TRIGGER = 79;
-
 const int ENT_CLIENT_HEALING_ORB = 80;
 
 const int ENT_CLIENT_MUTATOR = TE_CSQC_MUTATOR; // 99
diff --git a/qcsrc/common/minigames/cl_minigames.qc b/qcsrc/common/minigames/cl_minigames.qc
new file mode 100644 (file)
index 0000000..2fefd53
--- /dev/null
@@ -0,0 +1,406 @@
+#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
+bool 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 = name##_hud_board; \
+               minig.minigame_hud_status = name##_hud_status; \
+               minig.minigame_event = name##_client_event; \
+               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_Long(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();
+bool 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, active_minigame)) )
+               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 minigame_entremove()
+{
+       if ( self == active_minigame )
+               deactivate_minigame();
+}
+
+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 )
+       {
+               deactivate_minigame();
+       }
+       
+       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 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 = minigame_entremove;
+                       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);
+
+       if ( sf & MINIG_SF_CREATE )
+       {
+               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 ReadString
+#undef FIELD
+#undef MSLE
+
+string minigame_getWrappedLine(float w, vector theFontSize, textLengthUpToWidth_widthFunction_t tw)
+{
+       int last_word;
+       string s;
+       int take_until;
+       int 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);
+
+       for ( int 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, int 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, int 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, int 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, int 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, int 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"), "");
+       }
+}
diff --git a/qcsrc/common/minigames/cl_minigames.qh b/qcsrc/common/minigames/cl_minigames.qh
new file mode 100644 (file)
index 0000000..0c17401
--- /dev/null
@@ -0,0 +1,121 @@
+#ifndef CL_MINIGAMES_H
+#define CL_MINIGAMES_H
+
+#include "../../dpdefs/keycodes.qh"
+
+// 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
+bool 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, int 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, int 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, int drawflags );
+
+// Like drawcolorcodedstring but truncates the text to fit maxwidth
+void minigame_drawcolorcodedstring_trunc(float maxwidth, vector pos, string text, 
+       vector fontsize, float theAlpha, int 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, int 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 (file)
index 0000000..b7b7df1
--- /dev/null
@@ -0,0 +1,700 @@
+#include "minigames.qh"
+#include "../../client/mapvoting.qh"
+
+// whether the mouse is over the given panel
+bool 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
+// ====================================================================
+
+// Draws the minigame game 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
+// ====================================================================
+// Draws the minigame status panel
+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 newentry, entity prev)
+{
+       if ( !HUD_MinigameMenu_entries )
+       {
+               HUD_MinigameMenu_entries = newentry;
+               HUD_MinigameMenu_last_entry = newentry;
+               return;
+       }
+       
+       newentry.list_prev = prev;
+       newentry.list_next = prev.list_next;
+       if ( prev.list_next )
+               prev.list_next.list_prev = newentry;
+       else
+               HUD_MinigameMenu_last_entry = newentry;
+       prev.list_next = newentry;
+       
+}
+
+
+// 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
+bool 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
+                       HUD_MinigameMenu_last_entry = 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()
+{
+       deactivate_minigame();
+       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() )
+       {
+               entity e;
+               entity prev = self;
+               for(int 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
+bool 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;
+
+       vector color;
+       vector offset;
+       float itemh;
+       vector imgsz = '22 22 0'; // NOTE: if changed, edit where HUD_MinigameMenu_activeitem is selected
+       for ( entity 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;
+               con_keys = findkeysforcommand("toggleconsole", 0);
+               int keys = tokenize(con_keys); // findkeysforcommand returns data for this
+               for (int 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);
+}
+
+bool 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 (file)
index 0000000..d1899e7
--- /dev/null
@@ -0,0 +1,119 @@
+#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:
+       int <id>_server_event(entity minigame, string event, ...count)
+               see ../minigames.qh for a detailed explanation
+CSQC:
+       void <id>_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 <id>_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
+       int <id>_client_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
+       Don't add MINIG_SF_CREATE to SendFlags on your own
+* 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"
+#include "c4.qc"
+#include "pong.qc"
+#include "qto.qc"
+#include "ps.qc"
+#include "pp.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") \
+       MINIGAME(pong,"Pong") \
+       MINIGAME(c4,  "Connect Four") \
+       MINIGAME(qto, "Quinto") \
+       MINIGAME(ps,  "Peg Solitaire") \
+       MINIGAME(pp,  "Push-Pull") \
+       /*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)) \
+       MSLE(pong_paddle,FIELD(MINIG_SF_CREATE,Byte,team) FIELD(MINIG_SF_CREATE,Float,pong_length) FIELD(MINIG_SF_UPDATE,Vector2D,origin)) \
+       MSLE(pong_ball,FIELD(MINIG_SF_CREATE,Float,pong_length) FIELD(PONG_SF_BALLTEAM,Byte,team) FIELD(MINIG_SF_UPDATE, Vector2D, velocity) FIELD(MINIG_SF_UPDATE, Vector2D, origin)) \
+       MSLE(pong_ai, FIELD(MINIG_SF_CREATE,Byte,team) FIELD(PONG_SF_PLAYERSCORE, Long, pong_score)) \
+       /*empty line*/ 
diff --git a/qcsrc/common/minigames/minigame/c4.qc b/qcsrc/common/minigames/minigame/c4.qc
new file mode 100644 (file)
index 0000000..a153b39
--- /dev/null
@@ -0,0 +1,505 @@
+const float C4_TURN_PLACE = 0x0100; // player has to place a piece on the board
+const float C4_TURN_WIN   = 0x0200; // player has won
+const float C4_TURN_DRAW  = 0x0400; // no moves are possible
+const float C4_TURN_TYPE  = 0x0f00; // turn type mask
+
+const float C4_TURN_TEAM1 = 0x0001;
+const float C4_TURN_TEAM2 = 0x0002;
+const float C4_TURN_TEAM  = 0x000f; // turn team mask
+
+const int C4_LET_CNT = 7;
+const int C4_NUM_CNT = 6;
+const int C4_WIN_CNT = 4;
+
+const int C4_MAX_TILES = 42;
+
+const int C4_TILE_SIZE = 8;
+
+const int C4_TEAMS = 2;
+
+.int c4_npieces; // (minigame) number of pieces on the board (simplifies checking a draw)
+.int c4_nexteam; // (minigame) next team (used to change the starting team on following matches)
+
+// find connect 4 piece given its tile name
+entity c4_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
+bool c4_winning_piece(entity piece)
+{
+       int number = minigame_tile_number(piece.netname);
+       int letter = minigame_tile_letter(piece.netname);
+
+       int i;
+       entity top = piece;
+       entity left = piece;
+       entity topleft = piece;
+       entity botleft = piece;
+       for(i = number; i < C4_NUM_CNT; ++i)
+       {
+               entity p = c4_find_piece(piece.owner,minigame_tile_buildname(letter, i));
+               if(p.team == piece.team)
+                       top = p;
+               else break;
+       }
+
+       for(i = letter; i >= 0; --i)
+       {
+               entity p = c4_find_piece(piece.owner,minigame_tile_buildname(i, number));
+               if(p.team == piece.team)
+                       left = p;
+               else break;
+       }
+
+       int j;
+       for(i = letter, j = number; i >= 0, j >= 0; --i, --j)
+       {
+               entity p = c4_find_piece(piece.owner,minigame_tile_buildname(i, j));
+               if(p.team == piece.team)
+                       botleft = p;
+               else break;
+       }
+       for(i = letter, j = number; i >= 0, j < C4_NUM_CNT; --i, ++j)
+       {
+               entity p = c4_find_piece(piece.owner,minigame_tile_buildname(i, j));
+               if(p.team == piece.team)
+                       topleft = p;
+               else break;
+       }
+
+       // down
+       int found = 0;
+       for(i = minigame_tile_number(top.netname); i >= 0; --i)
+       {
+               if(c4_find_piece(piece.owner,minigame_tile_buildname(letter, i)).team == piece.team)
+                       ++found;
+               else break;
+       }
+
+       if(found >= C4_WIN_CNT)
+               return true;
+
+       // right
+       found = 0;
+       for(i = minigame_tile_letter(left.netname); i < C4_LET_CNT; ++i)
+       {
+               if(c4_find_piece(piece.owner,minigame_tile_buildname(i, number)).team == piece.team)
+                       ++found;
+               else break;
+       }
+
+       if(found >= C4_WIN_CNT)
+               return true;
+
+       // diagright down
+       found = 0;
+       for(i = minigame_tile_letter(topleft.netname), j = minigame_tile_number(topleft.netname); i < C4_LET_CNT, j >= 0; ++i, --j)
+       {
+               if(c4_find_piece(piece.owner,minigame_tile_buildname(i, j)).team == piece.team)
+                       ++found;
+               else break;
+       }
+
+       if(found >= C4_WIN_CNT)
+               return true;
+
+       // diagright up
+       found = 0;
+       for(i = minigame_tile_letter(botleft.netname), j = minigame_tile_number(botleft.netname); i < C4_LET_CNT, j < C4_NUM_CNT; ++i, ++j)
+       {
+               if(c4_find_piece(piece.owner,minigame_tile_buildname(i, j)).team == piece.team)
+                       ++found;
+               else break;
+       }
+
+       if(found >= C4_WIN_CNT)
+               return true;
+       
+       return false;
+}
+
+// check if the tile name is valid (6x7 grid)
+bool c4_valid_tile(string tile)
+{
+       if ( !tile )
+               return false;
+       float number = minigame_tile_number(tile);
+       float letter = minigame_tile_letter(tile);
+       return 0 <= number && number < C4_NUM_CNT && 0 <= letter && letter < C4_LET_CNT;
+}
+
+string c4_get_lowest_tile(entity minigame, string s)
+{
+       int i;
+       int end = 0;
+       for(i = C4_NUM_CNT; i >= 0; --i)
+       {
+               if(!c4_find_piece(minigame,minigame_tile_buildname(minigame_tile_letter(s), i)))
+               if(c4_find_piece(minigame,minigame_tile_buildname(minigame_tile_letter(s), i - 1)))
+               {
+                       end = i;
+                       break;
+               }
+       }
+       return minigame_tile_buildname(minigame_tile_letter(s), end);
+}
+
+// make a move
+void c4_move(entity minigame, entity player, string pos )
+{
+       pos = c4_get_lowest_tile(minigame, pos);
+
+       if ( minigame.minigame_flags & C4_TURN_PLACE )
+       if ( pos && player.team == (minigame.minigame_flags & C4_TURN_TEAM) )
+       {
+               if ( c4_valid_tile(pos) )
+               if ( !c4_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.c4_npieces++;
+                       minigame.c4_nexteam = minigame_next_team(player.team,C4_TEAMS);
+                       if ( c4_winning_piece(piece) )
+                       {
+                               minigame.minigame_flags = C4_TURN_WIN | player.team;
+                       }
+                       else if ( minigame.c4_npieces >= C4_MAX_TILES )
+                               minigame.minigame_flags = C4_TURN_DRAW;
+                       else
+                               minigame.minigame_flags = C4_TURN_PLACE | minigame.c4_nexteam;
+               }
+       }
+}
+
+#ifdef SVQC
+
+
+// required function, handle server side events
+int c4_server_event(entity minigame, string event, ...)
+{
+       switch(event)
+       {
+               case "start":
+               {
+                       minigame.minigame_flags = (C4_TURN_PLACE | C4_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":
+               {
+                       int pl_num = minigame_count_players(minigame);
+
+                       // Don't allow more than 2 players
+                       if(pl_num >= C4_TEAMS) { return false; }
+
+                       // Get the right team
+                       if(minigame.minigame_players)
+                               return minigame_next_team(minigame.minigame_players.team, C4_TEAMS);
+
+                       // Team 1 by default
+                       return 1;
+               }
+               case "cmd":
+               {
+                       switch(argv(0))
+                       {
+                               case "move": 
+                                       c4_move(minigame, ...(0,entity), ...(1,int) == 2 ? argv(1) : string_null ); 
+                                       return true;
+                       }
+
+                       return false;
+               }
+       }
+       
+       return false;
+}
+
+
+#elif defined(CSQC)
+
+string c4_curr_pos; // identifier of the tile under the mouse
+vector c4_boardpos; // HUD board position
+vector c4_boardsize;// HUD board size
+.int c4_checkwin; // Used to optimize checks to display a win
+
+// Required function, draw the game board
+void c4_hud_board(vector pos, vector mySize)
+{
+       minigame_hud_fitsqare(pos, mySize);
+       c4_boardpos = pos;
+       c4_boardsize = mySize;
+       
+       minigame_hud_simpleboard(pos,mySize,minigame_texture("c4/board_under"));
+
+       drawpic(pos, minigame_texture("c4/board_over"), mySize, '1 1 1', 1, 0);
+
+       vector tile_size = minigame_hud_denormalize_size('1 1 0' / C4_TILE_SIZE,pos,mySize);
+       vector tile_pos;
+
+       if ( (active_minigame.minigame_flags & C4_TURN_TEAM) == minigame_self.team )
+       if ( c4_valid_tile(c4_curr_pos) )
+       {
+               tile_pos = minigame_tile_pos(c4_curr_pos,C4_NUM_CNT,C4_LET_CNT);
+               tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
+               minigame_drawpic_centered( tile_pos,  
+                               minigame_texture(strcat("c4/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,C4_NUM_CNT,C4_LET_CNT);
+                       tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
+                       
+                       if ( active_minigame.minigame_flags & C4_TURN_WIN )
+                       if ( !e.c4_checkwin )
+                               e.c4_checkwin = c4_winning_piece(e) ? 1 : -1;
+                       
+                       float icon_color = 1;
+                       if ( e.c4_checkwin == -1 )
+                               icon_color = 0.4;
+                       else if ( e.c4_checkwin == 1 )
+                       {
+                               icon_color = 2;
+                               minigame_drawpic_centered( tile_pos, minigame_texture("c4/winglow"),
+                                               tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_ADDITIVE );
+                       }
+                               
+                       minigame_drawpic_centered( tile_pos,  
+                                       minigame_texture(strcat("c4/piece",ftos(e.team))),
+                                       tile_size, '1 1 1'*icon_color, panel_fg_alpha, DRAWFLAG_NORMAL );
+               }
+       }
+
+       if ( active_minigame.minigame_flags & C4_TURN_WIN )
+       {
+               vector winfs = hud_fontsize*2;
+               string playername = "";
+               FOREACH_MINIGAME_ENTITY(e)
+                       if ( e.classname == "minigame_player" && 
+                                       e.team == (active_minigame.minigame_flags & C4_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 c4_hud_status(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';
+
+       mypos = pos;
+       if ( (active_minigame.minigame_flags&C4_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*tile_size_y,'1 1 1',0.25,DRAWFLAG_ADDITIVE);
+
+       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,
+                               GetPlayerName(e.minigame_playerslot-1),
+                               player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
+                       
+                       mypos_y += player_fontsize_y;
+                       drawpic( mypos,  
+                                       minigame_texture(strcat("c4/piece",ftos(e.team))),
+                                       tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
+                       
+                       mypos_x += tile_size_x;
+               }
+       }
+}
+
+// Turn a set of flags into a help message
+string c4_turn_to_string(int turnflags)
+{
+       if ( turnflags & C4_TURN_DRAW )
+               return _("Draw");
+       
+       if ( turnflags & C4_TURN_WIN )
+       {
+               if ( (turnflags&C4_TURN_TEAM) != minigame_self.team )
+                       return _("You lost the game!");
+               return _("You win!");
+       }
+       
+       if ( (turnflags & C4_TURN_TEAM) != minigame_self.team )
+               return _("Wait for your opponent to make their move");
+       
+       if ( turnflags & C4_TURN_PLACE )
+               return _("Click on the game board to place your piece");
+       
+       return "";
+}
+
+// Make the correct move
+void c4_make_move(entity minigame)
+{
+       if ( minigame.minigame_flags == (C4_TURN_PLACE|minigame_self.team) )
+       {
+               minigame_cmd("move ",c4_curr_pos);
+       }
+}
+
+void c4_set_curr_pos(string s)
+{
+       if ( c4_curr_pos )
+               strunzone(c4_curr_pos);
+       if ( s )
+               s = strzone(s);
+       c4_curr_pos = s;
+}
+
+// Required function, handle client events
+int c4_client_event(entity minigame, string event, ...)
+{
+       switch(event)
+       {
+               case "activate":
+               {
+                       c4_set_curr_pos("");
+                       minigame.message = c4_turn_to_string(minigame.minigame_flags);
+                       return false;
+               }
+               case "key_pressed":
+               {
+                       if((minigame.minigame_flags & C4_TURN_TEAM) == minigame_self.team)
+                       {
+                               switch ( ...(0,int) )
+                               {
+                                       case K_RIGHTARROW:
+                                       case K_KP_RIGHTARROW:
+                                               if ( ! c4_curr_pos )
+                                                       c4_set_curr_pos(c4_get_lowest_tile(minigame, "a3"));
+                                               else
+                                                       c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_relative_tile(c4_curr_pos,1,0,C4_NUM_CNT,C4_LET_CNT)));
+                                               return true;
+                                       case K_LEFTARROW:
+                                       case K_KP_LEFTARROW:
+                                               if ( ! c4_curr_pos )
+                                                       c4_set_curr_pos(c4_get_lowest_tile(minigame, "c3"));
+                                               else
+                                                       c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_relative_tile(c4_curr_pos,-1,0,C4_NUM_CNT,C4_LET_CNT)));
+                                               return true;
+                                       /*case K_UPARROW:
+                                       case K_KP_UPARROW:
+                                               if ( ! c4_curr_pos )
+                                                       c4_set_curr_pos("a1");
+                                               else
+                                                       c4_set_curr_pos(minigame_relative_tile(c4_curr_pos,0,1,6,7));
+                                               return true;
+                                       case K_DOWNARROW:
+                                       case K_KP_DOWNARROW:
+                                               if ( ! c4_curr_pos )
+                                                       c4_set_curr_pos("a3");
+                                               else
+                                                       c4_set_curr_pos(minigame_relative_tile(c4_curr_pos,0,-1,6,7));
+                                               return true;*/
+                                       case K_ENTER:
+                                       case K_KP_ENTER:
+                                       case K_SPACE:
+                                               c4_make_move(minigame);
+                                               return true;
+                               }
+                       }
+
+                       return false;
+               }
+               case "mouse_pressed":
+               {
+                       if(...(0,int) == K_MOUSE1)
+                       {
+                               c4_make_move(minigame);
+                               return true;
+                       }
+
+                       return false;
+               }
+               case "mouse_moved":
+               {
+                       vector mouse_pos = minigame_hud_normalize(mousepos,c4_boardpos,c4_boardsize);
+                       if ( minigame.minigame_flags == (C4_TURN_PLACE|minigame_self.team) )
+                       {
+                               c4_set_curr_pos(c4_get_lowest_tile(minigame, minigame_tile_name(mouse_pos,C4_NUM_CNT,C4_LET_CNT)));
+                       }
+                       if ( ! c4_valid_tile(c4_curr_pos) )
+                               c4_set_curr_pos("");
+
+                       return true;
+               }
+               case "network_receive":
+               {
+                       entity sent = ...(0,entity);
+                       int sf = ...(1,int);
+                       if ( sent.classname == "minigame" )
+                       {
+                               if ( sf & MINIG_SF_UPDATE )
+                               {
+                                       sent.message = c4_turn_to_string(sent.minigame_flags);
+                                       if ( sent.minigame_flags & minigame_self.team )
+                                               minigame_prompt();
+                               }
+                       }
+
+                       return false;
+               }
+       }
+
+       return false;
+}
+
+#endif
\ No newline at end of file
diff --git a/qcsrc/common/minigames/minigame/nmm.qc b/qcsrc/common/minigames/minigame/nmm.qc
new file mode 100644 (file)
index 0000000..fc5df6b
--- /dev/null
@@ -0,0 +1,759 @@
+const int NMM_TURN_PLACE = 0x0100; // player has to place a piece on the board
+const int NMM_TURN_MOVE  = 0x0200; // player has to move a piece by one tile
+const int NMM_TURN_FLY   = 0x0400; // player has to move a piece anywhere
+const int NMM_TURN_TAKE  = 0x0800; // player has to take a non-mill piece
+const int NMM_TURN_TAKEANY=0x1000; // combine with NMM_TURN_TAKE, can take mill pieces
+const int NMM_TURN_WIN   = 0x2000; // player has won
+const int NMM_TURN_TYPE  = 0xff00;
+const int NMM_TURN_TEAM1 = 0x0001;
+const int NMM_TURN_TEAM2 = 0x0002;
+const int NMM_TURN_TEAM  = 0x00ff;
+
+const int NMM_PIECE_DEAD  = 0x0; // captured by the enemy
+const int NMM_PIECE_HOME  = 0x1; // not yet placed
+const int NMM_PIECE_BOARD = 0x2; // placed on the board
+
+.int  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)
+{
+       int number = minigame_tile_number(tile.netname);
+       int letter = minigame_tile_letter(tile.netname);
+       if ( number == letter || number+letter == 6 )
+       {
+               int 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)
+{
+       int letter = minigame_tile_letter(tile.netname);
+       int number = minigame_tile_number(tile.netname);
+       if ( letter == number || letter+number == 6 )
+       {
+               int 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, int 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, int offset, int skip )
+{
+       int letter = offset;
+       int number = offset;
+       for ( int i = 0; i < 3; i++ )
+       {
+               number = offset;
+               for ( int 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
+bool nmm_tile_adjacent(entity tile1, entity tile2)
+{
+               
+       int dnumber = fabs ( minigame_tile_number(tile1.netname) - minigame_tile_number(tile2.netname) );
+       int 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
+bool 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 true;
+               }
+       return false;
+}
+
+// Check if the given tile id appears in the string
+bool nmm_in_mill_string(entity tile, string s)
+{
+       int argc = tokenize(s);
+       for ( int 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 false;
+       }
+       return true;
+}
+
+// Check if a tile is in a mill
+bool 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, int teamn, int 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
+int nmm_count_pieces(entity minigame, int teamn, int pieceflags)
+{
+       int n = 0;
+       entity e = world;
+       while (( e = nmm_find_piece(e,minigame, teamn, pieceflags) ))
+               n++;
+       return n;
+}
+
+// required function, handle server side events
+int nmm_server_event(entity minigame, string event, ...)
+{
+       if ( event == "start" )
+       {
+               minigame.minigame_flags = NMM_TURN_PLACE|NMM_TURN_TEAM1;
+               nmm_init_tiles(minigame);
+               entity e;
+               for ( int 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" )
+       {
+               int 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);
+               int argc = ...(1,int);
+               entity tile = world;
+               entity piece = world;
+               bool move_ok = false;
+               
+               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 = false;
+                       }
+                       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 = true;
+                               }
+                       }
+                       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 = true;
+                                       }
+                               }
+                               
+                       }
+                       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 = true;
+                                       }
+                               }
+                               
+                       }
+                       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 = true;
+                               }
+                       }
+                       
+                       int nextteam = e.team % 2 + 1;
+                       int 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;
+                                       int 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
+bool nmm_valid_selection(entity tile)
+{
+       if ( ( tile.owner.minigame_flags & NMM_TURN_TEAM ) != minigame_self.team )
+               return false; // 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 true; //  movable tile
+               if ( nmm_fromtile ) // valid destination
+                       return !tile.nmm_tile_piece && nmm_tile_adjacent(nmm_fromtile,tile);
+               return false;
+       }
+       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 false;
+}
+
+// whether it should highlight valid tile selections
+bool nmm_draw_avaliable(entity tile)
+{
+       if ( ( tile.owner.minigame_flags & NMM_TURN_TEAM ) != minigame_self.team )
+               return false;
+       if ( (tile.owner.minigame_flags & NMM_TURN_TAKE) )
+               return true;
+       if ( (tile.owner.minigame_flags & (NMM_TURN_FLY|NMM_TURN_MOVE)) && nmm_fromtile )
+               return !tile.nmm_tile_piece;
+       return false;
+}
+
+// Required function, draw the game board
+void nmm_hud_board(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 nmm_hud_status(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(int 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
+int nmm_client_event(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,int) )
+               {
+                       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,int) == 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,int) & 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,int) & 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/pong.qc b/qcsrc/common/minigames/minigame/pong.qc
new file mode 100644 (file)
index 0000000..7cb37de
--- /dev/null
@@ -0,0 +1,670 @@
+// minigame flags
+const int PONG_STATUS_WAIT = 0x0010; // waiting for players to join
+const int PONG_STATUS_PLAY = 0x0020; // playing
+
+// send flags
+// (minigame_player) sent when reporting scores
+const int PONG_SF_PLAYERSCORE = MINIG_SF_CUSTOM;
+// (pong_ball) sent when changing team
+const int PONG_SF_BALLTEAM = MINIG_SF_CUSTOM;
+
+// keys
+const int PONG_KEY_INCREASE = 0x01; // Move down/right
+const int PONG_KEY_DECREASE = 0x02; // Move up/left
+const int PONG_KEY_BOTH     = 0x03; // Player jamming keys at ramdom
+
+// fields
+const int PONG_MAX_PLAYERS = 4;
+.int    pong_score;                    // (minigame_player) number of goals
+.int    pong_keys;                     // (client) pressed keys
+.entity pong_paddles[PONG_MAX_PLAYERS];// (minigame) paddles
+.float  pong_length;                   // (pong_paddle/pong_ball) size (0,1)
+.entity pong_ai_paddle;                // (pong_ai) controlled paddle entity
+
+#ifdef SVQC
+
+float autocvar_sv_minigames_pong_paddle_size;
+float autocvar_sv_minigames_pong_paddle_speed;
+
+float autocvar_sv_minigames_pong_ball_wait;
+float autocvar_sv_minigames_pong_ball_speed;
+float autocvar_sv_minigames_pong_ball_radius;
+float autocvar_sv_minigames_pong_ball_number;
+
+float autocvar_sv_minigames_pong_ai_thinkspeed;
+float autocvar_sv_minigames_pong_ai_tolerance;
+
+void pong_ball_think();
+
+// Throws a ball in a random direction and sets the think function
+void pong_ball_throw(entity ball)
+{
+       float angle;
+       do
+               angle = random()*M_PI*2;
+       while ( fabs(sin(angle)) < 0.17 || fabs(cos(angle)) < 0.17 );
+       ball.velocity_x = cos(angle)*autocvar_sv_minigames_pong_ball_speed;
+       ball.velocity_y = sin(angle)*autocvar_sv_minigames_pong_ball_speed;
+       ball.think = pong_ball_think;
+       ball.nextthink = time;
+       ball.team = 0;
+       ball.SendFlags |= MINIG_SF_UPDATE|PONG_SF_BALLTEAM;
+}
+
+// Think equivalent of pong_ball_throw, used to delay throws
+void pong_ball_throwthink()
+{
+       pong_ball_throw(self);
+}
+
+// Moves ball to the center and stops its motion
+void pong_ball_reset(entity ball)
+{
+       ball.velocity = '0 0 0';
+       ball.origin = '0.5 0.5 0';
+       ball.think = SUB_NullThink;
+       ball.team = 0;
+       ball.SendFlags |= MINIG_SF_UPDATE|PONG_SF_BALLTEAM;
+       ball.think = pong_ball_throwthink;
+       ball.nextthink = time + autocvar_sv_minigames_pong_ball_wait;
+}
+
+// Add the score to the given team in the minigame
+void pong_add_score(entity minigame, int team_thrower, int team_receiver, int delta)
+{
+       if ( !minigame )
+               return;
+       
+       if ( team_thrower == 0 )
+               team_thrower = team_receiver;
+       
+       if ( team_thrower == team_receiver )
+               delta *= -1;
+       
+       entity paddle_thrower = minigame.pong_paddles[team_thrower-1];
+       if ( paddle_thrower.realowner.minigame_players )
+       {
+               paddle_thrower.realowner.minigame_players.pong_score += delta;
+               paddle_thrower.realowner.minigame_players.SendFlags |= PONG_SF_PLAYERSCORE;
+       }
+}
+
+// get point in the box nearest to the given one (2D)
+vector box_nearest(vector box_min, vector box_max, vector p)
+{
+       return eX * ( p_x > box_max_x  ? box_max_x  : ( p_x < box_min_x ? box_min_x : p_x ) )
+               + eY * ( p_y > box_max_y  ? box_max_y  : ( p_y < box_min_y ? box_min_y : p_y ) );
+}
+
+void pong_paddle_bounce(entity ball, int pteam)
+{
+       switch(pteam)
+       {
+               case 1: ball.velocity_x = -fabs(ball.velocity_x); break;
+               case 2: ball.velocity_x = fabs(ball.velocity_x); break;
+               case 3: ball.velocity_y = fabs(ball.velocity_y); break;
+               case 4: ball.velocity_y = -fabs(ball.velocity_y); break;
+       }
+       
+       float angle = atan2(ball.velocity_y, ball.velocity_x);
+       angle += ( random() - 0.5 ) * 2 * M_PI/6;
+       float speed = vlen(ball.velocity);
+       
+       ball.velocity_y = speed * sin(angle);
+       ball.velocity_x = speed * cos(angle);
+}
+
+// checks if the ball hit the paddle for the given team
+bool pong_paddle_hit(entity ball, int pteam)
+{
+       entity paddle = ball.owner.pong_paddles[pteam-1];
+       if (!paddle)
+               return false;
+       vector near_point = box_nearest(paddle.mins+paddle.origin, 
+                                                                       paddle.maxs+paddle.origin, ball.origin);
+       return vlen(near_point-ball.origin) <= ball.pong_length ;
+}
+
+// Checks for a goal, when that happes adds scores and resets the ball
+bool pong_goal(entity ball, int pteam)
+{
+       entity paddle = ball.owner.pong_paddles[pteam-1];
+       if (!paddle)
+               return false;
+       
+       if ( !pong_paddle_hit(ball, pteam) )
+       {
+               pong_add_score(ball.owner ,ball.team, pteam, 1);
+               pong_ball_reset(ball);
+               return true;
+       }
+       
+       return false;
+}
+
+// Moves the ball around
+void pong_ball_think()
+{
+       float think_speed = autocvar_sys_ticrate;
+       self.nextthink = time + think_speed;
+       
+       self.origin_x += self.velocity_x * think_speed;
+       self.origin_y += self.velocity_y * think_speed;
+       self.SendFlags |= MINIG_SF_UPDATE;
+       
+       int i;
+       for ( i = 1; i <= PONG_MAX_PLAYERS; i++ )
+               if ( pong_paddle_hit(self, i) )
+               {
+                       pong_paddle_bounce(self,i);
+                       self.team = i;
+                       self.SendFlags |= PONG_SF_BALLTEAM;
+                       return;
+               }
+       
+       if ( self.origin_y <= self.pong_length )
+       {
+               if ( !pong_goal(self,3) )
+               {
+                       self.origin_y = self.pong_length;
+                       self.velocity_y *= -1;
+               }
+       }
+       else if ( self.origin_y >= 1-self.pong_length )
+       {
+               if ( !pong_goal(self,4) )
+               {
+                       self.origin_y = 1-self.pong_length;
+                       self.velocity_y *= -1;
+               }
+       }
+       
+       if ( self.origin_x <= self.pong_length )
+       {
+               if ( !pong_goal(self,2) )
+               {
+                        self.origin_x = self.pong_length;
+                        self.velocity_x *= -1;
+               }
+       }
+       else if ( self.origin_x >= 1-self.pong_length )
+       {
+               if ( !pong_goal(self,1) )
+               {
+                        self.origin_x = 1-self.pong_length;
+                        self.velocity_x *= -1;
+               }
+       }
+       
+}
+
+// AI action
+void pong_ai_think()
+{
+       float think_speed = autocvar_sv_minigames_pong_ai_thinkspeed;
+       self.nextthink = time + think_speed;
+       
+       float distance;
+       float next_distance;
+       float min_distance = 1;
+       entity ball = world;
+       entity mayball = world;
+       while ( ( mayball = findentity(mayball,owner,self.owner) ) )
+               if ( mayball.classname == "pong_ball" )
+               {
+                       distance = vlen(mayball.origin-self.pong_ai_paddle.origin);
+                       next_distance = vlen(mayball.origin+mayball.velocity-self.pong_ai_paddle.origin);
+                       if (  distance < min_distance && ( distance < 0.5 || next_distance < distance ) )
+                       {
+                               min_distance = distance;
+                               ball = mayball;
+                       }
+               }
+       
+       float target = 0.5;
+       float self_pos;
+       
+       
+       if ( self.team <= 2 )
+       {
+               if ( ball )
+                       target = ball.origin_y + ball.velocity_y*think_speed;
+               self_pos = self.pong_ai_paddle.origin_y;
+       }
+       else
+       {
+               if ( ball )
+                       target = ball.origin_x + ball.velocity_x*think_speed;
+               self_pos = self.pong_ai_paddle.origin_x;
+       }
+       
+       distance = self.pong_length/2 * autocvar_sv_minigames_pong_ai_tolerance
+               + autocvar_sv_minigames_pong_paddle_speed * think_speed;
+
+       if (target < self_pos - distance)
+               self.pong_keys = PONG_KEY_DECREASE;
+       else if (target > self_pos + distance)
+               self.pong_keys = PONG_KEY_INCREASE;
+       else
+               self.pong_keys = 0;
+}
+
+entity pong_ai_spawn(entity paddle)
+{
+       entity ai = msle_spawn(paddle.owner,"pong_ai");
+       ai.minigame_players = ai;
+       ai.team = paddle.team;
+       ai.think = pong_ai_think;
+       ai.nextthink = time;
+       ai.pong_ai_paddle = paddle;
+       
+       paddle.realowner = ai;
+       
+       return ai;
+}
+
+// Moves the paddle
+void pong_paddle_think()
+{
+       float think_speed = autocvar_sys_ticrate;
+       self.nextthink = time + think_speed;
+
+       if ( self.realowner.minigame_players.pong_keys == PONG_KEY_INCREASE || 
+                self.realowner.minigame_players.pong_keys == PONG_KEY_DECREASE )
+       {
+               float movement = autocvar_sv_minigames_pong_paddle_speed * think_speed;
+               float halflen = self.pong_length/2;
+       
+               if ( self.realowner.minigame_players.pong_keys == PONG_KEY_DECREASE )
+                       movement *= -1;
+               
+               if ( self.team > 2 )
+                       self.origin_x = bound(halflen, self.origin_x+movement, 1-halflen);
+               else
+                       self.origin_y = bound(halflen, self.origin_y+movement, 1-halflen);
+               
+               self.SendFlags |= MINIG_SF_UPDATE;
+       }
+}
+
+vector pong_team_to_box_halfsize(int nteam, float length, float width)
+{
+       if ( nteam > 2 )
+               return eY*width/2 + eX*length/2;
+       return eX*width/2 + eY*length/2;
+}
+
+vector pong_team_to_paddlepos(int nteam)
+{
+       switch(nteam)
+       {
+               case 1: return '0.99 0.5 0';
+               case 2: return '0.01 0.5 0';
+               case 3: return '0.5 0.01 0';
+               case 4: return '0.5 0.99 0';
+               default:return '0 0 0';
+       }
+}
+
+// Spawns a pong paddle
+// if real_player is world, the paddle is controlled by AI
+entity pong_paddle_spawn(entity minigame, int pl_team, entity real_player)
+{
+       entity paddle = msle_spawn(minigame,"pong_paddle");
+       paddle.pong_length = autocvar_sv_minigames_pong_paddle_size;
+       paddle.origin = pong_team_to_paddlepos(pl_team);
+       paddle.think = pong_paddle_think;
+       paddle.nextthink = time;
+       paddle.team = pl_team;
+       paddle.mins = pong_team_to_box_halfsize(pl_team,-paddle.pong_length,-1/16);
+       paddle.maxs = pong_team_to_box_halfsize(pl_team,paddle.pong_length,1/16);
+       
+       if ( real_player == world )
+               pong_ai_spawn(paddle);
+       else
+               paddle.realowner = real_player;
+       
+       minigame.pong_paddles[pl_team-1] = paddle;
+       
+       return paddle;
+
+}
+
+// required function, handle server side events
+int pong_server_event(entity minigame, string event, ...)
+{
+       switch (event)
+       {
+               case "start":
+               {
+                       minigame.minigame_flags |= PONG_STATUS_WAIT;
+                       return true;
+               }
+               case "join":
+               {
+                       // Don't allow joining a match that is already running
+                       if ( minigame.minigame_flags & PONG_STATUS_PLAY )
+                               return false;
+                       
+                       entity player = ...(0,entity);
+                       int i;
+                       for ( i = 0; i < PONG_MAX_PLAYERS; i++ )
+                       {
+                               if ( minigame.pong_paddles[i] == world )
+                               {
+                                       pong_paddle_spawn(minigame,i+1,player);
+                                       return i+1;
+                               }
+                       }
+                       
+                       return false;
+               }
+               case "part":
+               {
+                       entity player = ...(0,entity);
+                       entity paddle;
+                       entity ai;
+                       int i;
+                       for ( i = 0; i < PONG_MAX_PLAYERS; i++ )
+                       {
+                               paddle = minigame.pong_paddles[i];
+                               if ( paddle != world && paddle.realowner == player )
+                               {
+                                       ai = pong_ai_spawn(paddle);
+                                       ai.pong_score = player.minigame_players.pong_score;
+                                       break;
+                               }
+                                       
+                       }
+                       return false;
+               }
+               case "cmd":
+               {
+                       entity player = ...(0,entity);
+                       switch(argv(0))
+                       {
+                               case "throw":
+                                       if ( minigame.minigame_flags & PONG_STATUS_WAIT )
+                                       {
+                                               minigame.minigame_flags = PONG_STATUS_PLAY |
+                                                       (minigame.minigame_flags & ~PONG_STATUS_WAIT);
+                                               minigame.SendFlags |= MINIG_SF_UPDATE;
+                                               
+                                               int i;
+                                               entity ball;
+                                               for ( i = 0; i < autocvar_sv_minigames_pong_ball_number; i++ )
+                                               {
+                                                       ball = msle_spawn(minigame,"pong_ball");
+                                                       ball.pong_length = autocvar_sv_minigames_pong_ball_radius;
+                                                       pong_ball_reset(ball);
+                                               }
+                                       }
+                                       return true;
+                               case "+movei":
+                                       player.pong_keys |= PONG_KEY_INCREASE;
+                                       return true;
+                               case "+moved":
+                                       player.pong_keys |= PONG_KEY_DECREASE;
+                                       return true;
+                               case "-movei":
+                                       player.pong_keys &= ~PONG_KEY_INCREASE;
+                                       return true;
+                               case "-moved":
+                                       player.pong_keys &= ~PONG_KEY_DECREASE;
+                                       return true;
+                               case "pong_aimore":
+                               {
+                                       int i;
+                                       if ( minigame.minigame_flags & PONG_STATUS_WAIT )
+                                               for ( i = 0; i < PONG_MAX_PLAYERS; i++ )
+                                               {
+                                                       if ( minigame.pong_paddles[i] == world )
+                                                       {
+                                                               pong_paddle_spawn(minigame,i+1,world);
+                                                               return true;
+                                                       }
+                                               }
+                                       sprint(player.minigame_players,"Cannot spawn AI\n");
+                                       return true;
+                               }
+                               case "pong_ailess":
+                               {
+                                       if ( minigame.minigame_flags & PONG_STATUS_WAIT )
+                                       {
+                                               entity paddle;
+                                               int i;
+                                               for ( i = PONG_MAX_PLAYERS-1; i >= 0; i-- )
+                                               {
+                                                       paddle = minigame.pong_paddles[i];
+                                                       if ( paddle != world && 
+                                                               paddle.realowner.classname == "pong_ai" )
+                                                       {
+                                                               minigame.pong_paddles[i] = world;
+                                                               remove(paddle.realowner);
+                                                               remove(paddle);
+                                                               return true;
+                                                       }
+                                               }
+                                       }
+                                       sprint(player.minigame_players,"Cannot remove AI\n");
+                                       return true;
+                               }
+                                               
+                       }
+                       return false;
+               }
+               case "network_send":
+               {
+                       entity sent = ...(0,entity);
+                       int sf = ...(1,int);
+                       if ( sent.classname == "minigame_player" && (sf & PONG_SF_PLAYERSCORE ) )
+                       {
+                               WriteLong(MSG_ENTITY,sent.pong_score);
+                       }
+                       return false;
+               }
+       }
+       return false;
+}
+
+
+#elif defined(CSQC)
+
+#include "waypointsprites.qh" // drawrotpic
+
+float pong_team_to_angle(int nteam)
+{
+       switch(nteam)
+       {
+               default:
+               case 1: return 0;
+               case 2: return M_PI;
+               case 3: return M_PI*3/2;
+               case 4: return M_PI/2;
+       }
+}
+
+vector pong_team_to_color(int nteam)
+{
+       switch(nteam)
+       {
+               case 1: return '1 0 0';
+               case 2: return '0 0 1';
+               case 3: return '1 1 0';
+               case 4: return '1 0 1';
+               default:return '1 1 1';
+       }
+}
+
+// Required function, draw the game board
+void pong_hud_board(vector pos, vector mySize)
+{
+       minigame_hud_fitsqare(pos, mySize);
+       minigame_hud_simpleboard(pos,mySize,minigame_texture("pong/board"));
+       
+       entity e;
+       vector obj_pos;
+       vector obj_size;
+       FOREACH_MINIGAME_ENTITY(e)
+       {
+               if ( e.classname == "pong_ball" )
+               {
+                       // Note: 4*radius = 2*diameter because the image is large enough to fit the glow around the ball
+                       obj_size =  minigame_hud_denormalize_size('4 4 0'*e.pong_length,pos,mySize);
+                       obj_pos = minigame_hud_denormalize(e.origin,pos,mySize);
+                       
+                       minigame_drawpic_centered( obj_pos, minigame_texture("pong/ball"),
+                                       obj_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
+                       
+                       minigame_drawpic_centered( obj_pos, minigame_texture("pong/ball-glow"),
+                                       obj_size, pong_team_to_color(e.team), 
+                                       panel_fg_alpha, DRAWFLAG_ADDITIVE );
+               }
+               else if ( e.classname == "pong_paddle" )
+               {
+                       obj_pos = minigame_hud_denormalize(e.origin,pos,mySize);
+                       obj_size = minigame_hud_denormalize_size(eX / 16 + eY*e.pong_length,pos,mySize);
+                       
+                       drawrotpic(obj_pos, pong_team_to_angle(e.team), minigame_texture("pong/paddle-glow"), 
+                               obj_size, obj_size/2, pong_team_to_color(e.team), 
+                               panel_fg_alpha, DRAWFLAG_ADDITIVE );
+                       
+                       drawrotpic(obj_pos, pong_team_to_angle(e.team), minigame_texture("pong/paddle"), 
+                               obj_size, obj_size/2, '1 1 1', 
+                               panel_fg_alpha, DRAWFLAG_NORMAL );
+                       
+               }
+       }
+}
+
+// Required function, draw the game status panel
+void pong_hud_status(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);
+       ts_y += hud_fontsize_y;
+       pos_y += ts_y;
+       mySize_y -= ts_y;
+       
+       vector player_fontsize = hud_fontsize * 1.75;
+       ts_y = ( mySize_y - PONG_MAX_PLAYERS*player_fontsize_y ) / PONG_MAX_PLAYERS;
+       ts_x = mySize_x;
+       vector mypos;
+
+       entity e;
+       FOREACH_MINIGAME_ENTITY(e)
+       {
+               if ( e.classname == "minigame_player" || e.classname == "pong_ai" )
+               {
+                       mypos = pos;
+                       mypos_y  += (e.team-1) * (player_fontsize_y + ts_y);
+                       
+                       drawfill(mypos, ts, pong_team_to_color(e.team), 0.25, DRAWFLAG_ADDITIVE);
+                       
+                       minigame_drawcolorcodedstring_trunc(mySize_x,mypos,
+                               (e.minigame_playerslot ? GetPlayerName(e.minigame_playerslot-1) : _("AI")),
+                               player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
+                       
+                       drawstring(mypos+eY*player_fontsize_y,ftos(e.pong_score),'48 48 0',
+                                          '0.7 0.84 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+                       
+                       if ( e == minigame_self )
+                               drawborderlines(1, mypos, ts, pong_team_to_color(e.team), 1, DRAWFLAG_NORMAL);
+               }
+       }
+}
+
+// convert minigame flags to a message
+string pong_message(int mgflags)
+{
+       string rmessage = "";
+       if (mgflags & PONG_STATUS_WAIT)
+               rmessage = _("Press ^1Start Match^7 to start the match with the current players");
+       return rmessage;
+}
+
+// Required function, handle client events
+int pong_client_event(entity minigame, string event, ...)
+{
+       switch(event)
+       {
+               case "activate":
+                       return false;
+               case "key_pressed":
+                       switch ( ...(0,int) )
+                       {
+                               case K_UPARROW:
+                               case K_KP_UPARROW:
+                               case K_LEFTARROW:
+                               case K_KP_LEFTARROW:
+                                       minigame_cmd("+moved");
+                                       return true;
+                               case K_DOWNARROW:
+                               case K_KP_DOWNARROW:
+                               case K_RIGHTARROW:
+                               case K_KP_RIGHTARROW:
+                                       minigame_cmd("+movei");
+                                       return true;
+                       }
+                       return false;
+               case "key_released":
+                       switch ( ...(0,int) )
+                       {
+                               case K_UPARROW:
+                               case K_KP_UPARROW:
+                               case K_LEFTARROW:
+                               case K_KP_LEFTARROW:
+                                       minigame_cmd("-moved");
+                                       return true;
+                               case K_DOWNARROW:
+                               case K_KP_DOWNARROW:
+                               case K_RIGHTARROW:
+                               case K_KP_RIGHTARROW:
+                                       minigame_cmd("-movei");
+                                       return true;
+                       }
+                       return false;
+               case "network_receive":
+               {
+                       entity sent = ...(0,entity);
+                       int sf = ...(1,int);
+                       if ( sent.classname == "minigame_player" && (sf & PONG_SF_PLAYERSCORE ) )
+                       {
+                               sent.pong_score = ReadLong();
+                       }
+                       else if ( sent.classname == "minigame" )
+                       {
+                               if ( sf & MINIG_SF_UPDATE )
+                               {
+                                       sent.message = pong_message(sent.minigame_flags);
+                               }
+                       }
+                       return false;
+               }
+               case "menu_show":
+               {
+                       HUD_MinigameMenu_CustomEntry(...(0,entity),_("Start Match"),"pong_throw");
+                       HUD_MinigameMenu_CustomEntry(...(0,entity),_("Add AI player"),"pong_aimore");
+                       HUD_MinigameMenu_CustomEntry(...(0,entity),_("Remove AI player"),"pong_ailess");
+                       return false;
+               }
+               case "menu_click":
+               {
+                       string cmd = ...(0,string);
+                       if( cmd == "pong_throw" && minigame.minigame_flags & PONG_STATUS_WAIT )
+                       {
+                               minigame_cmd("throw");
+                       }
+                       else if ( cmd == "pong_aimore" || cmd == "pong_ailess" )
+                       {
+                               minigame_cmd(cmd);
+                       }
+                       return false;
+               }
+       }
+
+       return false;
+}
+#endif
\ No newline at end of file
diff --git a/qcsrc/common/minigames/minigame/pp.qc b/qcsrc/common/minigames/minigame/pp.qc
new file mode 100644 (file)
index 0000000..eacb985
--- /dev/null
@@ -0,0 +1,592 @@
+const int PP_TURN_PLACE = 0x0100; // player has to place a piece on the board
+const int PP_TURN_WIN   = 0x0200; // player has won
+const int PP_TURN_DRAW  = 0x0400; // players have equal scores
+const int PP_TURN_NEXT  = 0x0800; // a player wants to start a new match
+const int PP_TURN_TYPE  = 0x0f00; // turn type mask
+
+const int PP_TURN_TEAM1 = 0x0001;
+const int PP_TURN_TEAM2 = 0x0002;
+const int PP_TURN_TEAM  = 0x000f; // turn team mask
+
+const int PP_BLOCKED_TEAM = 5; // there won't ever be a 5th team, so we can abuse this
+
+const int PP_LET_CNT = 7;
+const int PP_NUM_CNT = 7;
+
+const int PP_TILE_SIZE = 7;
+
+.int pp_team1_score;
+.int pp_team2_score;
+
+.int pp_nexteam;
+
+.entity pp_curr_piece; // identifier for the current target piece
+
+// find tic tac toe piece given its tile name
+entity pp_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;
+}
+
+// check if the tile name is valid (3x3 grid)
+bool pp_valid_tile(string tile)
+{
+       if ( !tile )
+               return 0;
+       int number = minigame_tile_number(tile);
+       int letter = minigame_tile_letter(tile);
+       return 0 <= number && number < PP_NUM_CNT && 0 <= letter && letter < PP_LET_CNT;
+}
+
+// Checks if the given piece completes a row
+bool pp_winning_piece(entity piece)
+{
+       int number = minigame_tile_number(piece.netname);
+       int letter = minigame_tile_letter(piece.netname);
+
+       // here goes
+       if(!pp_valid_tile(minigame_tile_buildname(letter-1,number)) || pp_find_piece(piece.owner,minigame_tile_buildname(letter-1,number)).team == 5)
+       if(!pp_valid_tile(minigame_tile_buildname(letter+1,number)) || pp_find_piece(piece.owner,minigame_tile_buildname(letter+1,number)).team == 5)
+       if(!pp_valid_tile(minigame_tile_buildname(letter,number-1)) || pp_find_piece(piece.owner,minigame_tile_buildname(letter,number-1)).team == 5)
+       if(!pp_valid_tile(minigame_tile_buildname(letter,number+1)) || pp_find_piece(piece.owner,minigame_tile_buildname(letter,number+1)).team == 5)
+       if(!pp_valid_tile(minigame_tile_buildname(letter+1,number+1)) || pp_find_piece(piece.owner,minigame_tile_buildname(letter+1,number+1)).team == 5)
+       if(!pp_valid_tile(minigame_tile_buildname(letter-1,number-1)) || pp_find_piece(piece.owner,minigame_tile_buildname(letter-1,number-1)).team == 5)
+       if(!pp_valid_tile(minigame_tile_buildname(letter+1,number-1)) || pp_find_piece(piece.owner,minigame_tile_buildname(letter+1,number-1)).team == 5)
+       if(!pp_valid_tile(minigame_tile_buildname(letter-1,number+1)) || pp_find_piece(piece.owner,minigame_tile_buildname(letter-1,number+1)).team == 5)
+               return true;
+       
+       return false;
+}
+
+bool pp_valid_move(entity minigame, string pos)
+{
+       if(!pp_valid_tile(pos))
+               return false;
+       if(pp_find_piece(minigame,pos).team == 5)
+               return false;
+
+       entity current = minigame.pp_curr_piece;
+       if(!current)
+               return true; // no current piece? allow the move anywhere
+
+       int number = minigame_tile_number(pos);
+       int letter = minigame_tile_letter(pos);
+
+       if( (pp_find_piece(minigame,minigame_tile_buildname(letter-1,number)) == current)
+       ||      (pp_find_piece(minigame,minigame_tile_buildname(letter+1,number)) == current)
+       ||      (pp_find_piece(minigame,minigame_tile_buildname(letter,number-1)) == current)
+       ||      (pp_find_piece(minigame,minigame_tile_buildname(letter,number+1)) == current)
+       ||      (pp_find_piece(minigame,minigame_tile_buildname(letter+1,number+1)) == current)
+       ||      (pp_find_piece(minigame,minigame_tile_buildname(letter-1,number-1)) == current)
+       ||      (pp_find_piece(minigame,minigame_tile_buildname(letter+1,number-1)) == current)
+       ||      (pp_find_piece(minigame,minigame_tile_buildname(letter-1,number+1)) == current)
+       ) { return true; }
+
+       return false;
+}
+
+// make a move
+void pp_move(entity minigame, entity player, string pos )
+{
+       if ( minigame.minigame_flags & PP_TURN_PLACE )
+       if ( pos && player.team == (minigame.minigame_flags & PP_TURN_TEAM) )
+       {
+               if ( pp_valid_move(minigame,pos))
+               {
+                       entity existing = pp_find_piece(minigame,pos);
+
+                       if(existing && existing.team != 5)
+                       {
+                               if(existing.team == 1)
+                                       minigame.pp_team1_score++;
+                               if(existing.team == 2)
+                                       minigame.pp_team2_score++;
+                       }
+
+                       if(minigame.pp_curr_piece)
+                       {
+                               minigame.pp_curr_piece.cnt = 0;
+                               minigame.pp_curr_piece.team = 5;
+                               minigame_server_sendflags(minigame.pp_curr_piece,MINIG_SF_ALL);
+                       }
+
+                       if(existing)
+                       {
+                               if(existing.netname) { strunzone(existing.netname); }
+                               remove(existing);
+                       }
+
+                       entity piece = msle_spawn(minigame,"minigame_board_piece");
+                       piece.cnt = 1;
+                       piece.team = player.team; // temporary
+                       piece.netname = strzone(pos);
+                       minigame_server_sendflags(piece,MINIG_SF_ALL);
+                       minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
+                       minigame.pp_nexteam = minigame_next_team(player.team,2);
+                       minigame.pp_curr_piece = piece;
+                       if ( pp_winning_piece(piece) )
+                       {
+                               if(minigame.pp_team1_score == minigame.pp_team2_score)
+                                       minigame.minigame_flags = PP_TURN_DRAW;
+                               else
+                                       minigame.minigame_flags = PP_TURN_WIN | ((minigame.pp_team1_score > minigame.pp_team2_score) ? 1 : 2);
+                       }
+                       else
+                               minigame.minigame_flags = PP_TURN_PLACE | minigame.pp_nexteam;
+               }
+       }
+}
+
+void pp_setup_pieces(entity minigame)
+{
+       int i, t; // letter, number
+       for(i = 0; i < PP_LET_CNT; ++i)
+       for(t = 0; t < PP_NUM_CNT; ++t)
+       {
+               bool t2_true = ((i == 0 || i == 6) && t > 0 && t < 6);
+               bool t1_true = (i > 0 && i < 6 && (t == 0 || t == 6));
+
+               if(t1_true || t2_true)
+               {
+                       entity piece = msle_spawn(minigame,"minigame_board_piece");
+                       piece.team = ((t1_true) ? 1 : 2);
+                       piece.netname = strzone(minigame_tile_buildname(i,t));
+                       minigame_server_sendflags(piece,MINIG_SF_ALL);
+                       minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
+               }
+       }
+
+       minigame.pp_curr_piece = world;
+}
+
+// request a new match
+void pp_next_match(entity minigame, entity player)
+{
+#ifdef SVQC
+       // on multiplayer matches, wait for both players to agree
+       if ( minigame.minigame_flags & (PP_TURN_WIN|PP_TURN_DRAW) )
+       {
+               minigame.minigame_flags = PP_TURN_NEXT | player.team;
+               minigame.SendFlags |= MINIG_SF_UPDATE;
+       }
+       else if ( (minigame.minigame_flags & PP_TURN_NEXT) &&
+                       !( minigame.minigame_flags & player.team ) )
+#endif
+       {
+               minigame.minigame_flags = PP_TURN_PLACE | minigame.pp_nexteam;
+               minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
+               entity e = world;
+               while ( ( e = findentity(e,owner,minigame) ) )
+                       if ( e.classname == "minigame_board_piece" )
+                               remove(e);
+               minigame.pp_team1_score = 0;
+               minigame.pp_team2_score = 0;
+
+               pp_setup_pieces(minigame);
+       }
+}
+
+#ifdef SVQC
+
+
+// required function, handle server side events
+int pp_server_event(entity minigame, string event, ...)
+{
+       switch(event)
+       {
+               case "start":
+               {
+                       minigame.minigame_flags = (PP_TURN_PLACE | PP_TURN_TEAM1);
+                       pp_setup_pieces(minigame);
+                       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":
+               {
+                       int pl_num = minigame_count_players(minigame);
+
+                       // 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": 
+                                       pp_move(minigame, ...(0,entity), ...(1,int) == 2 ? argv(1) : string_null ); 
+                                       return true;
+                               case "next":
+                                       pp_next_match(minigame,...(0,entity));
+                                       return true;
+                       }
+
+                       return false;
+               }
+               case "network_send":
+               {
+                       entity sent = ...(0,entity);
+                       int sf = ...(1,int);
+                       if ( sent.classname == "minigame" && (sf & MINIG_SF_UPDATE ) )
+                       {
+                               WriteByte(MSG_ENTITY,sent.pp_team1_score);
+                               WriteByte(MSG_ENTITY,sent.pp_team2_score);
+                       }
+                       else if(sent.classname == "minigame_board_piece")
+                               WriteByte(MSG_ENTITY,sent.cnt);
+                       return false;
+               }
+       }
+       
+       return false;
+}
+
+
+#elif defined(CSQC)
+
+string pp_curr_pos; // identifier of the tile under the mouse
+vector pp_boardpos; // HUD board position
+vector pp_boardsize;// HUD board size
+.int pp_checkwin; // Used to optimize checks to display a win
+
+// Required function, draw the game board
+void pp_hud_board(vector pos, vector mySize)
+{
+       minigame_hud_fitsqare(pos, mySize);
+       pp_boardpos = pos;
+       pp_boardsize = mySize;
+       
+       minigame_hud_simpleboard(pos,mySize,minigame_texture("pp/board"));
+
+       vector tile_size = minigame_hud_denormalize_size('1 1 0'/PP_TILE_SIZE,pos,mySize);
+       vector tile_pos;
+
+       active_minigame.pp_curr_piece = world;
+       entity e;
+       FOREACH_MINIGAME_ENTITY(e)
+       if(e.classname == "minigame_board_piece")
+       if(e.cnt)
+       {
+               active_minigame.pp_curr_piece = e;
+               break;
+       }
+
+       FOREACH_MINIGAME_ENTITY(e)
+       {
+               if ( e.classname == "minigame_board_piece" )
+               {
+                       tile_pos = minigame_tile_pos(e.netname,PP_LET_CNT,PP_NUM_CNT);
+                       tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
+
+                       vector tile_color = '1 1 1';
+                       switch(e.team)
+                       {
+                               case 1: tile_color = '1 0.3 0.3'; break;
+                               case 2: tile_color = '0.3 0.3 1'; break;
+                               // 3, 4 coming later?
+                       }
+                       
+                       string tile_name = strcat("pp/piece",ftos(e.team));
+                       if(e.team == 5) { tile_name = "pp/piece_taken"; }
+
+                       if(e == active_minigame.pp_curr_piece)
+                       {
+                               tile_name = "pp/piece_current";
+
+                               // draw the splat too
+                               minigame_drawpic_centered( tile_pos,  
+                                               minigame_texture("pp/piece_taken"),
+                                               tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
+                       }
+
+                       minigame_drawpic_centered( tile_pos,  
+                                       minigame_texture(tile_name),
+                                       tile_size, tile_color, panel_fg_alpha, DRAWFLAG_NORMAL );
+               }
+       }
+
+       if ( (active_minigame.minigame_flags & PP_TURN_TEAM) == minigame_self.team )
+       if ( pp_valid_move(active_minigame, pp_curr_pos) )
+       {
+               tile_pos = minigame_tile_pos(pp_curr_pos,PP_LET_CNT,PP_NUM_CNT);
+               tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
+               minigame_drawpic_centered( tile_pos,  
+                               minigame_texture("pp/piece_current"),
+                               tile_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL );
+       }
+       else if(pp_valid_tile(pp_curr_pos))
+       {
+               tile_pos = minigame_tile_pos(pp_curr_pos,PP_LET_CNT,PP_NUM_CNT);
+               tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
+               minigame_drawpic_centered( tile_pos,  
+                               minigame_texture("pp/piece_selected"),
+                               tile_size, '1 1 1', panel_fg_alpha / 2, DRAWFLAG_NORMAL );
+       }
+
+       if ( active_minigame.minigame_flags & PP_TURN_WIN )
+       {
+               vector winfs = hud_fontsize*2;
+               string playername = "";
+               FOREACH_MINIGAME_ENTITY(e)
+                       if ( e.classname == "minigame_player" && 
+                                       e.team == (active_minigame.minigame_flags & PP_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 pp_hud_status(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';
+
+       mypos = pos;
+       if ( (active_minigame.minigame_flags&PP_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*tile_size_y,'1 1 1',0.25,DRAWFLAG_ADDITIVE);
+
+       entity e;
+       FOREACH_MINIGAME_ENTITY(e)
+       {
+               if ( e.classname == "minigame_player" )
+               {
+                       vector tile_color = '1 1 1';
+                       switch(e.team)
+                       {
+                               case 1: tile_color = '1 0.3 0.3'; break;
+                               case 2: tile_color = '0.3 0.3 1'; break;
+                               // 3, 4 coming later?
+                       }
+
+                       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);
+                       
+                       mypos_y += player_fontsize_y;
+                       drawpic( mypos,  
+                                       minigame_texture(strcat("pp/piece",ftos(e.team))),
+                                       tile_size, tile_color, panel_fg_alpha, DRAWFLAG_NORMAL );
+                       
+                       mypos_x += tile_size_x;
+                       int myscore = 0;
+                       if(e.team == 1) { myscore = active_minigame.pp_team1_score; }
+                       if(e.team == 2) { myscore = active_minigame.pp_team2_score; }
+                       
+                       drawstring(mypos,ftos(myscore),tile_size,
+                                          '0.7 0.84 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+               }
+       }
+}
+
+// Turn a set of flags into a help message
+string pp_turn_to_string(int turnflags)
+{
+       if ( turnflags & PP_TURN_DRAW )
+               return _("Draw");
+       
+       if ( turnflags & PP_TURN_WIN )
+       {
+               if ( (turnflags&PP_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 & PP_TURN_NEXT )
+       {
+               if ( (turnflags&PP_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 & PP_TURN_TEAM) != minigame_self.team )
+               return _("Wait for your opponent to make their move");
+       
+       if ( turnflags & PP_TURN_PLACE )
+               return _("Click on the game board to place your piece");
+       
+       return "";
+}
+
+// Make the correct move
+void pp_make_move(entity minigame)
+{
+       if ( minigame.minigame_flags == (PP_TURN_PLACE|minigame_self.team) )
+       {
+               minigame_cmd("move ",pp_curr_pos);
+       }
+}
+
+void pp_set_curr_pos(string s)
+{
+       if ( pp_curr_pos )
+               strunzone(pp_curr_pos);
+       if ( s )
+               s = strzone(s);
+       pp_curr_pos = s;
+}
+
+// Required function, handle client events
+int pp_client_event(entity minigame, string event, ...)
+{
+       switch(event)
+       {
+               case "activate":
+               {
+                       pp_set_curr_pos("");
+                       minigame.message = pp_turn_to_string(minigame.minigame_flags);
+                       return false;
+               }
+               case "key_pressed":
+               {
+                       if((minigame.minigame_flags & PP_TURN_TEAM) == minigame_self.team)
+                       {
+                               switch ( ...(0,int) )
+                               {
+                                       case K_RIGHTARROW:
+                                       case K_KP_RIGHTARROW:
+                                               if ( ! pp_curr_pos )
+                                                       pp_set_curr_pos("a3");
+                                               else
+                                                       pp_set_curr_pos(minigame_relative_tile(pp_curr_pos,1,0,PP_LET_CNT,PP_NUM_CNT));
+                                               return true;
+                                       case K_LEFTARROW:
+                                       case K_KP_LEFTARROW:
+                                               if ( ! pp_curr_pos )
+                                                       pp_set_curr_pos("c3");
+                                               else
+                                                       pp_set_curr_pos(minigame_relative_tile(pp_curr_pos,-1,0,PP_LET_CNT,PP_NUM_CNT));
+                                               return true;
+                                       case K_UPARROW:
+                                       case K_KP_UPARROW:
+                                               if ( ! pp_curr_pos )
+                                                       pp_set_curr_pos("a1");
+                                               else
+                                                       pp_set_curr_pos(minigame_relative_tile(pp_curr_pos,0,1,PP_LET_CNT,PP_NUM_CNT));
+                                               return true;
+                                       case K_DOWNARROW:
+                                       case K_KP_DOWNARROW:
+                                               if ( ! pp_curr_pos )
+                                                       pp_set_curr_pos("a3");
+                                               else
+                                                       pp_set_curr_pos(minigame_relative_tile(pp_curr_pos,0,-1,PP_LET_CNT,PP_NUM_CNT));
+                                               return true;
+                                       case K_ENTER:
+                                       case K_KP_ENTER:
+                                       case K_SPACE:
+                                               pp_make_move(minigame);
+                                               return true;
+                               }
+                       }
+
+                       return false;
+               }
+               case "mouse_pressed":
+               {
+                       if(...(0,int) == K_MOUSE1)
+                       {
+                               pp_make_move(minigame);
+                               return true;
+                       }
+
+                       return false;
+               }
+               case "mouse_moved":
+               {
+                       vector mouse_pos = minigame_hud_normalize(mousepos,pp_boardpos,pp_boardsize);
+                       if ( minigame.minigame_flags == (PP_TURN_PLACE|minigame_self.team) )
+                               pp_set_curr_pos(minigame_tile_name(mouse_pos,PP_LET_CNT,PP_NUM_CNT));
+                       if ( ! pp_valid_tile(pp_curr_pos) )
+                               pp_set_curr_pos("");
+
+                       return true;
+               }
+               case "network_receive":
+               {
+                       entity sent = ...(0,entity);
+                       int sf = ...(1,int);
+                       if ( sent.classname == "minigame" )
+                       {
+                               if ( sf & MINIG_SF_UPDATE )
+                               {
+                                       sent.message = pp_turn_to_string(sent.minigame_flags);
+                                       if ( sent.minigame_flags & minigame_self.team )
+                                               minigame_prompt();
+                                       sent.pp_team1_score = ReadByte();
+                                       sent.pp_team2_score = ReadByte();
+                               }
+                       }
+                       else if(sent.classname == "minigame_board_piece")
+                       {
+                               sent.cnt = ReadByte();
+                               if(sent.cnt)
+                                       minigame.pp_curr_piece = sent;
+                       }
+
+                       return false;
+               }
+               case "menu_show":
+               {
+                       HUD_MinigameMenu_CustomEntry(...(0,entity),_("Next Match"),"next");
+                       return false;
+               }
+               case "menu_click":
+               {
+                       if(...(0,string) == "next")
+                               minigame_cmd("next");
+                       return false;
+               }
+       }
+
+       return false;
+}
+
+#endif
\ No newline at end of file
diff --git a/qcsrc/common/minigames/minigame/ps.qc b/qcsrc/common/minigames/minigame/ps.qc
new file mode 100644 (file)
index 0000000..3acef2f
--- /dev/null
@@ -0,0 +1,620 @@
+const float PS_TURN_MOVE  = 0x0100; // player has to click on a piece on the board
+const float PS_TURN_WIN   = 0x0200; // player has won
+const float PS_TURN_DRAW  = 0x0400; // player can make no more moves
+const float PS_TURN_TYPE  = 0x0f00; // turn type mask
+
+const int PS_LET_CNT = 7;
+const int PS_NUM_CNT = 7;
+
+const int PS_TILE_SIZE = 8;
+
+// find same game piece given its tile name
+entity ps_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;
+}
+
+bool ps_draw(entity minigame)
+{
+       int valid = 0;
+       entity e = world;
+       while( ( e = findentity(e,owner,minigame) ) )
+               if( e.classname == "minigame_board_piece" )
+               {
+                       ++valid;
+               }
+
+       return ((valid > 0) ? true : false);
+}
+
+bool ps_tile_blacklisted(string tile)
+{
+       int number = minigame_tile_number(tile);
+       int letter = minigame_tile_letter(tile);
+       if(letter < 2)
+               if(number < 2)
+                       return true;
+               else if(number > PS_NUM_CNT - 3)
+                       return true;
+       if(letter > PS_LET_CNT - 3)
+               if(number < 2)
+                       return true;
+               else if(number > PS_NUM_CNT - 3)
+                       return true;
+
+       return false;
+}
+
+// check if the tile name is valid (5x5 grid)
+bool ps_valid_tile(string tile)
+{
+       if ( !tile )
+               return false;
+       if(ps_tile_blacklisted(tile))
+               return false;
+       float number = minigame_tile_number(tile);
+       float letter = minigame_tile_letter(tile);
+       return 0 <= number && number < PS_NUM_CNT && 0 <= letter && letter < PS_LET_CNT;
+}
+
+// Checks if the given piece completes a row
+bool ps_winning_piece(entity minigame)
+{
+       //int number = minigame_tile_number(piece.netname);
+       //int letter = minigame_tile_letter(piece.netname);
+
+       entity e = world;
+       while ( ( e = findentity(e,owner,minigame) ) )
+               if ( e.classname == "minigame_board_piece" )
+               {
+                       int number = minigame_tile_number(e.netname);
+                       int letter = minigame_tile_letter(e.netname);
+                       string try = minigame_tile_buildname(letter - 1, number);
+                       if(ps_find_piece(minigame,try))
+                       {
+                               try = minigame_tile_buildname(letter - 2, number);
+                               if(ps_valid_tile(try) && !ps_find_piece(minigame,try))
+                                       return false; // a move is valid, abort!
+                       }
+                       try = minigame_tile_buildname(letter + 1, number);
+                       if(ps_find_piece(minigame,try))
+                       {
+                               try = minigame_tile_buildname(letter + 2, number);
+                               if(ps_valid_tile(try) && !ps_find_piece(minigame,try))
+                                       return false; // a move is valid, abort!
+                       }
+                       try = minigame_tile_buildname(letter, number - 1);
+                       if(ps_find_piece(minigame,try))
+                       {
+                               try = minigame_tile_buildname(letter, number - 2);
+                               if(ps_valid_tile(try) && !ps_find_piece(minigame,try))
+                                       return false; // a move is valid, abort!
+                       }
+                       try = minigame_tile_buildname(letter, number + 1);
+                       if(ps_find_piece(minigame,try))
+                       {
+                               try = minigame_tile_buildname(letter, number + 2);
+                               if(ps_valid_tile(try) && !ps_find_piece(minigame,try))
+                                       return false; // a move is valid, abort!
+                       }
+               }
+       
+       return true;
+}
+
+void ps_setup_pieces(entity minigame)
+{
+       int i, t;
+       for(i = 0; i < PS_NUM_CNT; ++i)
+       for(t = 0; t < PS_LET_CNT; ++t)
+       {
+               string try = minigame_tile_buildname(i,t);
+               if(!ps_valid_tile(try))
+                       continue;
+               if(i == floor(PS_NUM_CNT * 0.5) && t == floor(PS_LET_CNT * 0.5))
+                       continue; // middle piece is empty
+               entity piece = msle_spawn(minigame,"minigame_board_piece");
+               piece.team = 1; // init default team?
+               piece.netname = strzone(minigame_tile_buildname(t,i));
+               minigame_server_sendflags(piece,MINIG_SF_ALL);
+       }
+
+       minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
+}
+
+bool ps_move_piece(entity minigame, entity piece, string pos, int leti, int numb)
+{
+       if(!piece)
+               return false;
+       if(ps_find_piece(minigame, pos))
+               return false;
+       entity middle = ps_find_piece(minigame, minigame_tile_buildname(leti,numb));
+       if(!middle)
+               return false;
+
+       if(middle.netname) { strunzone(middle.netname); }
+       remove(middle);
+
+       if(piece.netname) { strunzone(piece.netname); }
+       piece.netname = strzone(pos);
+
+       minigame_server_sendflags(piece,MINIG_SF_ALL);
+
+       return true;
+}
+
+// make a move
+void ps_move(entity minigame, entity player, string thepiece, string pos )
+{
+       if ( minigame.minigame_flags & PS_TURN_MOVE )
+       if ( pos )
+       {
+               if ( ps_valid_tile(pos) )
+               if ( !ps_find_piece(minigame, pos) && ps_find_piece(minigame, thepiece) )
+               {
+                       entity piece = ps_find_piece(minigame, thepiece);
+                       int number = minigame_tile_number(thepiece);
+                       int letter = minigame_tile_letter(thepiece);
+                       bool done = false;
+                       string try;
+
+                       try = minigame_tile_buildname(letter-1,number);
+                       if(ps_find_piece(minigame,try))
+                       {
+                               try = minigame_tile_buildname(letter-2,number);
+                               if(ps_valid_tile(try) && try == pos)
+                                       done = ps_move_piece(minigame, piece, pos, letter - 1, number);
+                       }
+                       try = minigame_tile_buildname(letter+1,number);
+                       if(!done && ps_find_piece(minigame,try))
+                       {
+                               try = minigame_tile_buildname(letter+2,number);
+                               if(ps_valid_tile(try) && try == pos)
+                                       done = ps_move_piece(minigame, piece, pos, letter + 1, number);
+                       }
+                       try = minigame_tile_buildname(letter,number-1);
+                       if(!done && ps_find_piece(minigame,try))
+                       {
+                               try = minigame_tile_buildname(letter,number-2);
+                               if(ps_valid_tile(try) && try == pos)
+                                       done = ps_move_piece(minigame, piece, pos, letter, number - 1);
+                       }
+                       try = minigame_tile_buildname(letter,number+1);
+                       if(!done && ps_find_piece(minigame,try))
+                       {
+                               try = minigame_tile_buildname(letter,number+2);
+                               if(ps_valid_tile(try) && try == pos)
+                                       done = ps_move_piece(minigame, piece, pos, letter, number + 1);
+                       }
+
+                       if(!done)
+                               return; // didn't make a move
+
+                       minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
+
+                       if ( ps_winning_piece(minigame) )
+                       {
+                               if(ps_draw(minigame))
+                                       minigame.minigame_flags = PS_TURN_DRAW;
+                               else
+                                       minigame.minigame_flags = PS_TURN_WIN;
+                       }
+                       else
+                               minigame.minigame_flags = PS_TURN_MOVE;
+               }
+       }
+}
+
+#ifdef SVQC
+
+
+// required function, handle server side events
+int ps_server_event(entity minigame, string event, ...)
+{
+       switch(event)
+       {
+               case "start":
+               {
+                       ps_setup_pieces(minigame);
+                       minigame.minigame_flags = PS_TURN_MOVE;
+                       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":
+               {
+                       int pl_num = minigame_count_players(minigame);
+
+                       // Don't allow more than 1 player
+                       if(pl_num >= 1) { return false; }
+
+                       // Team 1 by default
+                       return 1;
+               }
+               case "cmd":
+               {
+                       switch(argv(0))
+                       {
+                               case "move": 
+
+                                       ps_move(minigame, ...(0,entity), (...(1,int) == 3 ? argv(1) : string_null), (...(1,int) == 3 ? argv(2) : string_null));
+                                       return true;
+                       }
+
+                       return false;
+               }
+       }
+       
+       return false;
+}
+
+
+#elif defined(CSQC)
+
+entity ps_curr_piece; // identifier for the currently selected piece
+string ps_curr_pos; // identifier of the tile under the mouse
+vector ps_boardpos; // HUD board position
+vector ps_boardsize;// HUD board size
+
+// Required function, draw the game board
+void ps_hud_board(vector pos, vector mySize)
+{
+       minigame_hud_fitsqare(pos, mySize);
+       ps_boardpos = pos;
+       ps_boardsize = mySize;
+       
+       minigame_hud_simpleboard(pos,mySize,minigame_texture("ps/board"));
+
+       vector tile_size = minigame_hud_denormalize_size('1 1 0' / PS_TILE_SIZE,pos,mySize);
+       vector tile_pos;
+
+       bool valid = ps_valid_tile(ps_curr_pos);
+       bool highlight = false;
+       if(valid)
+       {
+               string try;
+               int number = minigame_tile_number(ps_curr_pos);
+               int letter = minigame_tile_letter(ps_curr_pos);
+               try = minigame_tile_buildname(letter-1,number);
+               if(ps_find_piece(active_minigame,try))
+               {
+                       try = minigame_tile_buildname(letter-2,number);
+                       if(ps_valid_tile(try) && !ps_find_piece(active_minigame,try))
+                               highlight = true;
+               }
+               try = minigame_tile_buildname(letter+1,number);
+               if(ps_find_piece(active_minigame,try))
+               {
+                       try = minigame_tile_buildname(letter+2,number);
+                       if(ps_valid_tile(try) && !ps_find_piece(active_minigame,try))
+                               highlight = true;
+               }
+               try = minigame_tile_buildname(letter,number-1);
+               if(ps_find_piece(active_minigame,try))
+               {
+                       try = minigame_tile_buildname(letter,number-2);
+                       if(ps_valid_tile(try) && !ps_find_piece(active_minigame,try))
+                               highlight = true;
+               }
+               try = minigame_tile_buildname(letter,number+1);
+               if(ps_find_piece(active_minigame,try))
+               {
+                       try = minigame_tile_buildname(letter,number+2);
+                       if(ps_valid_tile(try) && !ps_find_piece(active_minigame,try))
+                               highlight = true;
+               }
+       }
+       bool draw_pos = false;
+       if(ps_curr_piece && valid && !ps_find_piece(active_minigame, ps_curr_pos))
+       {
+               string try; // sigh
+               int numb = minigame_tile_number(ps_curr_piece.netname);
+               int leti = minigame_tile_letter(ps_curr_piece.netname);
+
+               try = minigame_tile_buildname(leti-1,numb);
+               if(ps_find_piece(active_minigame,try))
+               {
+                       try = minigame_tile_buildname(leti-2,numb);
+                       if(try == ps_curr_pos)
+                               draw_pos = true;
+               }
+               try = minigame_tile_buildname(leti+1,numb);
+               if(ps_find_piece(active_minigame,try))
+               {
+                       try = minigame_tile_buildname(leti+2,numb);
+                       if(try == ps_curr_pos)
+                               draw_pos = true;
+               }
+               try = minigame_tile_buildname(leti,numb-1);
+               if(ps_find_piece(active_minigame,try))
+               {
+                       try = minigame_tile_buildname(leti,numb-2);
+                       if(try == ps_curr_pos)
+                               draw_pos = true;
+               }
+               try = minigame_tile_buildname(leti,numb+1);
+               if(ps_find_piece(active_minigame,try))
+               {
+                       try = minigame_tile_buildname(leti,numb+2);
+                       if(try == ps_curr_pos)
+                               draw_pos = true;
+               }
+       }
+       
+       entity e;
+       FOREACH_MINIGAME_ENTITY(e)
+       {
+               if ( e.classname == "minigame_board_piece" )
+               {
+                       tile_pos = minigame_tile_pos(e.netname,PS_NUM_CNT,PS_LET_CNT);
+                       tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
+
+                       vector tile_color = '1 1 1';
+
+                       if(highlight)
+                       if(e.netname == ps_curr_pos)
+                       if(ps_curr_piece.netname != ps_curr_pos)
+                       {
+                               minigame_drawpic_centered( tile_pos,  
+                                               minigame_texture("ps/tile_available"),
+                                               tile_size, tile_color, panel_fg_alpha, DRAWFLAG_NORMAL );
+                       }
+                       if(e == ps_curr_piece)
+                       {
+                               minigame_drawpic_centered( tile_pos,  
+                                               minigame_texture("ps/tile_selected"),
+                                               tile_size, tile_color, panel_fg_alpha, DRAWFLAG_ADDITIVE );
+                       }
+
+                       minigame_drawpic_centered( tile_pos,  
+                                       minigame_texture("ps/piece"),
+                                       tile_size * 0.8, tile_color, panel_fg_alpha, DRAWFLAG_NORMAL );
+               }
+       }
+
+       if(draw_pos)
+       {
+               tile_pos = minigame_tile_pos(ps_curr_pos,PS_NUM_CNT,PS_LET_CNT);
+               tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
+
+               minigame_drawpic_centered(tile_pos,
+                               minigame_texture("ps/piece"),
+                               tile_size * 0.8, '0.5 0.5 0.5', panel_fg_alpha, DRAWFLAG_NORMAL);
+       }
+
+       if ( ( active_minigame.minigame_flags & PS_TURN_WIN ) || ( active_minigame.minigame_flags & PS_TURN_DRAW ) )
+       {
+               int remaining = 0;
+               FOREACH_MINIGAME_ENTITY(e)
+                       if(e.classname == "minigame_board_piece")
+                               ++remaining;
+
+               vector winfs = hud_fontsize*2;
+               string remaining_text;
+               if(active_minigame.minigame_flags & PS_TURN_WIN)
+                       remaining_text = "All pieces cleared!";
+               else
+                       remaining_text = strcat("Remaining pieces: ", ftos(remaining));
+               
+               vector win_pos = pos+eY*(mySize_y-winfs_y)/2;
+               vector win_sz;
+               win_sz = minigame_drawcolorcodedstring_wrapped(mySize_x,win_pos,
+                       sprintf("Game over! %s", remaining_text), 
+                       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("Game over! %s", remaining_text), 
+                       winfs, panel_fg_alpha, DRAWFLAG_NORMAL, 0.5);
+       }
+}
+
+
+// Required function, draw the game status panel
+void ps_hud_status(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';
+
+       mypos = pos;
+       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*tile_size_y,'1 1 1',0.25,DRAWFLAG_ADDITIVE);
+
+       int remaining = 0;
+       entity e;
+       FOREACH_MINIGAME_ENTITY(e)
+       {
+               if(e.classname == "minigame_board_piece")
+               {
+                       ++remaining;
+               }
+       }
+
+       FOREACH_MINIGAME_ENTITY(e)
+       {
+               if ( e.classname == "minigame_player" )
+               {
+                       mypos = pos;
+                       minigame_drawcolorcodedstring_trunc(mySize_x,mypos,
+                               GetPlayerName(e.minigame_playerslot-1),
+                               player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
+                       
+                       mypos_y += player_fontsize_y;
+                       //drawpic( mypos,  
+                       //              minigame_texture("ps/piece"),
+                       //              tile_size, '1 0 0', panel_fg_alpha, DRAWFLAG_NORMAL );
+                       
+                       //mypos_x += tile_size_x;
+
+                       drawstring(mypos,sprintf(_("Pieces left: %s"), ftos(remaining)),'28 28 0',
+                                          '0.7 0.84 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+               }
+       }
+}
+
+// Turn a set of flags into a help message
+string ps_turn_to_string(int turnflags)
+{
+       if (turnflags & PS_TURN_DRAW )
+               return _("No more valid moves");
+
+       if ( turnflags & PS_TURN_WIN )
+               return _("Well done, you win!");
+       
+       if ( turnflags & PS_TURN_MOVE )
+               return _("Jump a piece over another to capture it");
+       
+       return "";
+}
+
+// Make the correct move
+void ps_make_move(entity minigame)
+{
+       if ( minigame.minigame_flags == PS_TURN_MOVE )
+       {
+               entity piece = ps_find_piece(minigame,ps_curr_pos);
+               if(!ps_curr_piece || piece)
+                       ps_curr_piece = ps_find_piece(minigame,ps_curr_pos);
+               else
+               {
+                       minigame_cmd("move ", ps_curr_piece.netname, " ", ps_curr_pos);
+                       ps_curr_piece = world;
+               }
+       }
+}
+
+void ps_set_curr_pos(string s)
+{
+       if ( ps_curr_pos )
+               strunzone(ps_curr_pos);
+       if ( s )
+               s = strzone(s);
+       ps_curr_pos = s;
+}
+
+// Required function, handle client events
+int ps_client_event(entity minigame, string event, ...)
+{
+       switch(event)
+       {
+               case "activate":
+               {
+                       ps_set_curr_pos("");
+                       ps_curr_piece = world;
+                       minigame.message = ps_turn_to_string(minigame.minigame_flags);
+                       return false;
+               }
+               case "key_pressed":
+               {
+                       //if((minigame.minigame_flags & PS_TURN_TEAM) == minigame_self.team)
+                       {
+                               switch ( ...(0,int) )
+                               {
+                                       case K_RIGHTARROW:
+                                       case K_KP_RIGHTARROW:
+                                               if ( ! ps_curr_pos )
+                                                       ps_set_curr_pos("a3");
+                                               else
+                                                       ps_set_curr_pos( minigame_relative_tile(ps_curr_pos,1,0,PS_NUM_CNT,PS_LET_CNT));
+                                               return true;
+                                       case K_LEFTARROW:
+                                       case K_KP_LEFTARROW:
+                                               if ( ! ps_curr_pos )
+                                                       ps_set_curr_pos("c3");
+                                               else
+                                                       ps_set_curr_pos(minigame_relative_tile(ps_curr_pos,-1,0,PS_NUM_CNT,PS_LET_CNT));
+                                               return true;
+                                       case K_UPARROW:
+                                       case K_KP_UPARROW:
+                                               if ( ! ps_curr_pos )
+                                                       ps_set_curr_pos("a1");
+                                               else
+                                                       ps_set_curr_pos(minigame_relative_tile(ps_curr_pos,0,1,PS_NUM_CNT,PS_LET_CNT));
+                                               return true;
+                                       case K_DOWNARROW:
+                                       case K_KP_DOWNARROW:
+                                               if ( ! ps_curr_pos )
+                                                       ps_set_curr_pos("a3");
+                                               else
+                                                       ps_set_curr_pos(minigame_relative_tile(ps_curr_pos,0,-1,PS_NUM_CNT,PS_LET_CNT));
+                                               return true;
+                                       case K_ENTER:
+                                       case K_KP_ENTER:
+                                       case K_SPACE:
+                                               ps_make_move(minigame);
+                                               return true;
+                               }
+                       }
+
+                       return false;
+               }
+               case "mouse_pressed":
+               {
+                       if(...(0,int) == K_MOUSE1)
+                       {
+                               ps_make_move(minigame);
+                               return true;
+                       }
+
+                       return false;
+               }
+               case "mouse_moved":
+               {
+                       vector mouse_pos = minigame_hud_normalize(mousepos,ps_boardpos,ps_boardsize);
+                       if ( minigame.minigame_flags == PS_TURN_MOVE )
+                       {
+                               ps_set_curr_pos(minigame_tile_name(mouse_pos,PS_NUM_CNT,PS_LET_CNT));
+                       }
+                       if ( ! ps_valid_tile(ps_curr_pos) )
+                               ps_set_curr_pos("");
+
+                       return true;
+               }
+               case "network_receive":
+               {
+                       entity sent = ...(0,entity);
+                       int sf = ...(1,int);
+                       if ( sent.classname == "minigame" )
+                       {
+                               if ( sf & MINIG_SF_UPDATE )
+                               {
+                                       sent.message = ps_turn_to_string(sent.minigame_flags);
+                                       if ( sent.minigame_flags & minigame_self.team )
+                                               minigame_prompt();
+                               }
+                       }
+
+                       return false;
+               }
+       }
+
+       return false;
+}
+
+#endif
\ No newline at end of file
diff --git a/qcsrc/common/minigames/minigame/qto.qc b/qcsrc/common/minigames/minigame/qto.qc
new file mode 100644 (file)
index 0000000..cef71df
--- /dev/null
@@ -0,0 +1,461 @@
+const float QTO_TURN_MOVE  = 0x0100; // player has to click on a piece on the board
+const float QTO_TURN_WIN   = 0x0200; // player has won
+const float QTO_TURN_TYPE  = 0x0f00; // turn type mask
+
+const int QTO_SF_PLAYERSCORE = MINIG_SF_CUSTOM;
+
+const int QTO_LET_CNT = 5;
+const int QTO_NUM_CNT = 5;
+
+const int QTO_TILE_SIZE = 8;
+
+.int qto_moves;
+
+// find same game piece given its tile name
+entity qto_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
+bool qto_winning_piece(entity minigame)
+{
+       //int number = minigame_tile_number(piece.netname);
+       //int letter = minigame_tile_letter(piece.netname);
+
+       entity e = world;
+       while ( ( e = findentity(e,owner,minigame) ) )
+               if ( e.classname == "minigame_board_piece" )
+               {
+                       if(!e.cnt)
+                               return false;
+               }
+       
+       return true;
+}
+
+// check if the tile name is valid (5x5 grid)
+bool qto_valid_tile(string tile)
+{
+       if ( !tile )
+               return false;
+       float number = minigame_tile_number(tile);
+       float letter = minigame_tile_letter(tile);
+       return 0 <= number && number < QTO_NUM_CNT && 0 <= letter && letter < QTO_LET_CNT;
+}
+
+void qto_setup_pieces(entity minigame)
+{
+       int i, t;
+       for(i = 0; i < QTO_NUM_CNT; ++i)
+       for(t = 0; t < QTO_LET_CNT; ++t)
+       {
+               entity piece = msle_spawn(minigame,"minigame_board_piece");
+               piece.team = 1; // init default team?
+               piece.cnt = 0; // initialize cnt
+               piece.netname = strzone(minigame_tile_buildname(t,i));
+               minigame_server_sendflags(piece,MINIG_SF_ALL);
+       }
+
+       minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
+}
+
+void qto_add_score(entity minigame, int thescore)
+{
+#ifdef SVQC
+       if(!minigame)
+               return;
+       if(minigame.minigame_players)
+       {
+               minigame.minigame_players.qto_moves += thescore;
+               minigame.minigame_players.SendFlags |= QTO_SF_PLAYERSCORE;
+       }
+#endif
+}
+
+// make a move
+void qto_move(entity minigame, entity player, string pos )
+{
+       if ( minigame.minigame_flags & QTO_TURN_MOVE )
+       if ( pos )
+       {
+               if ( qto_valid_tile(pos) )
+               if ( qto_find_piece(minigame, pos) )
+               {
+                       entity piece;
+               #define DO_JUNK \
+                       if(piece) \
+                       { \
+                               piece.cnt = (piece.cnt) ? 0 : 1; \
+                               minigame_server_sendflags(piece,MINIG_SF_UPDATE); \
+                       }
+
+                       int number = minigame_tile_number(pos);
+                       int letter = minigame_tile_letter(pos);
+                       piece = qto_find_piece(minigame, pos);
+                       DO_JUNK
+                       piece = qto_find_piece(minigame, minigame_tile_buildname(letter-1,number));
+                       DO_JUNK
+                       piece = qto_find_piece(minigame, minigame_tile_buildname(letter+1,number));
+                       DO_JUNK
+                       piece = qto_find_piece(minigame, minigame_tile_buildname(letter,number-1));
+                       DO_JUNK
+                       piece = qto_find_piece(minigame, minigame_tile_buildname(letter,number+1));
+                       DO_JUNK
+
+                       qto_add_score(minigame,1); // add 1 move score
+
+                       minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
+
+                       if ( qto_winning_piece(minigame) )
+                       {
+                               minigame.minigame_flags = QTO_TURN_WIN;
+                       }
+                       else
+                               minigame.minigame_flags = QTO_TURN_MOVE;
+               }
+       }
+}
+
+// restart match
+void qto_restart_match(entity minigame, entity player)
+{
+       minigame.minigame_flags = QTO_TURN_MOVE;
+       minigame_server_sendflags(minigame,MINIG_SF_UPDATE);
+       entity e = world;
+       while ( ( e = findentity(e,owner,minigame) ) )
+               if ( e.classname == "minigame_board_piece" )
+                       remove(e);
+
+       qto_setup_pieces(minigame);
+#ifdef SVQC
+       if(minigame.minigame_players)
+       {
+               minigame.minigame_players.qto_moves = 0;
+               minigame.minigame_players.SendFlags |= QTO_SF_PLAYERSCORE;
+       }
+#endif
+}
+
+#ifdef SVQC
+
+
+// required function, handle server side events
+int qto_server_event(entity minigame, string event, ...)
+{
+       switch(event)
+       {
+               case "start":
+               {
+                       qto_setup_pieces(minigame);
+                       minigame.minigame_flags = QTO_TURN_MOVE;
+                       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":
+               {
+                       int pl_num = minigame_count_players(minigame);
+
+                       // Don't allow more than 1 player
+                       if(pl_num >= 1) { return false; }
+
+                       // Team 1 by default
+                       return 1;
+               }
+               case "cmd":
+               {
+                       switch(argv(0))
+                       {
+                               case "move": 
+                                       qto_move(minigame, ...(0,entity), ...(1,int) == 2 ? argv(1) : string_null ); 
+                                       return true;
+                               case "restart":
+                                       qto_restart_match(minigame,...(0,entity));
+                                       return true;
+                       }
+
+                       return false;
+               }
+               case "network_send":
+               {
+                       entity sent = ...(0,entity);
+                       int sf = ...(1,int);
+                       if ( sent.classname == "minigame_board_piece" && (sf & MINIG_SF_UPDATE) )
+                       {
+                               WriteByte(MSG_ENTITY,sent.cnt);
+                       }
+                       else if ( sent.classname == "minigame_player" && (sf & QTO_SF_PLAYERSCORE ) )
+                       {
+                               WriteLong(MSG_ENTITY,sent.qto_moves);
+                       }
+                       return false;
+               }
+       }
+       
+       return false;
+}
+
+
+#elif defined(CSQC)
+
+string qto_curr_pos; // identifier of the tile under the mouse
+vector qto_boardpos; // HUD board position
+vector qto_boardsize;// HUD board size
+
+// Required function, draw the game board
+void qto_hud_board(vector pos, vector mySize)
+{
+       minigame_hud_fitsqare(pos, mySize);
+       qto_boardpos = pos;
+       qto_boardsize = mySize;
+       
+       minigame_hud_simpleboard(pos,mySize,minigame_texture("qto/board"));
+
+       vector tile_size = minigame_hud_denormalize_size('1 1 0' / QTO_TILE_SIZE,pos,mySize);
+       vector tile_pos;
+
+       bool valid = qto_valid_tile(qto_curr_pos);
+       int number = minigame_tile_number(qto_curr_pos);
+       int letter = minigame_tile_letter(qto_curr_pos);
+       string pos1 = minigame_tile_buildname(letter-1,number);
+       string pos2 = minigame_tile_buildname(letter+1,number);
+       string pos3 = minigame_tile_buildname(letter,number-1);
+       string pos4 = minigame_tile_buildname(letter,number+1);
+       
+       entity e;
+       FOREACH_MINIGAME_ENTITY(e)
+       {
+               if ( e.classname == "minigame_board_piece" )
+               {
+                       tile_pos = minigame_tile_pos(e.netname,QTO_NUM_CNT,QTO_LET_CNT);
+                       tile_pos = minigame_hud_denormalize(tile_pos,pos,mySize);
+
+                       vector tile_color = '0.4 0.4 0.4';
+
+                       if(valid)
+                       switch(e.netname)
+                       {
+                               case qto_curr_pos:
+                               case pos1: case pos2: case pos3: case pos4:
+                                       tile_color = '0.8 0.8 0.8';
+                                       break;
+                       }
+                               
+                       minigame_drawpic_centered( tile_pos,  
+                                       minigame_texture(strcat("qto/piece", ftos(e.cnt))),
+                                       tile_size, tile_color, panel_fg_alpha, DRAWFLAG_NORMAL );
+               }
+       }
+}
+
+
+// Required function, draw the game status panel
+void qto_hud_status(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';
+
+       mypos = pos;
+       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*tile_size_y,'1 1 1',0.25,DRAWFLAG_ADDITIVE);
+
+       entity e;
+       FOREACH_MINIGAME_ENTITY(e)
+       {
+               if ( e.classname == "minigame_player" )
+               {
+                       mypos = pos;
+                       minigame_drawcolorcodedstring_trunc(mySize_x,mypos,
+                               GetPlayerName(e.minigame_playerslot-1),
+                               player_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
+                       
+                       mypos_y += player_fontsize_y;
+                       //drawpic( mypos,  
+                       //              minigame_texture("qto/piece"),
+                       //              tile_size, '1 0 0', panel_fg_alpha, DRAWFLAG_NORMAL );
+                       
+                       //mypos_x += tile_size_x;
+
+                       drawstring(mypos,sprintf(_("Moves: %s"), ftos(e.qto_moves)),'32 32 0',
+                                          '0.7 0.84 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+               }
+       }
+}
+
+// Turn a set of flags into a help message
+string qto_turn_to_string(int turnflags)
+{
+       if ( turnflags & QTO_TURN_WIN )
+               return _("Well done, you win!");
+       
+       if ( turnflags & QTO_TURN_MOVE )
+               return _("Turn all the angry faces into happy faces");
+       
+       return "";
+}
+
+// Make the correct move
+void qto_make_move(entity minigame)
+{
+       if ( minigame.minigame_flags == QTO_TURN_MOVE )
+       {
+               minigame_cmd("move ",qto_curr_pos);
+       }
+}
+
+void qto_set_curr_pos(string s)
+{
+       if ( qto_curr_pos )
+               strunzone(qto_curr_pos);
+       if ( s )
+               s = strzone(s);
+       qto_curr_pos = s;
+}
+
+// Required function, handle client events
+int qto_client_event(entity minigame, string event, ...)
+{
+       switch(event)
+       {
+               case "activate":
+               {
+                       qto_set_curr_pos("");
+                       minigame.message = qto_turn_to_string(minigame.minigame_flags);
+                       return false;
+               }
+               case "key_pressed":
+               {
+                       //if((minigame.minigame_flags & QTO_TURN_TEAM) == minigame_self.team)
+                       {
+                               switch ( ...(0,int) )
+                               {
+                                       case K_RIGHTARROW:
+                                       case K_KP_RIGHTARROW:
+                                               if ( ! qto_curr_pos )
+                                                       qto_set_curr_pos("a3");
+                                               else
+                                                       qto_set_curr_pos( minigame_relative_tile(qto_curr_pos,1,0,QTO_NUM_CNT,QTO_LET_CNT));
+                                               return true;
+                                       case K_LEFTARROW:
+                                       case K_KP_LEFTARROW:
+                                               if ( ! qto_curr_pos )
+                                                       qto_set_curr_pos("c3");
+                                               else
+                                                       qto_set_curr_pos(minigame_relative_tile(qto_curr_pos,-1,0,QTO_NUM_CNT,QTO_LET_CNT));
+                                               return true;
+                                       case K_UPARROW:
+                                       case K_KP_UPARROW:
+                                               if ( ! qto_curr_pos )
+                                                       qto_set_curr_pos("a1");
+                                               else
+                                                       qto_set_curr_pos(minigame_relative_tile(qto_curr_pos,0,1,QTO_NUM_CNT,QTO_LET_CNT));
+                                               return true;
+                                       case K_DOWNARROW:
+                                       case K_KP_DOWNARROW:
+                                               if ( ! qto_curr_pos )
+                                                       qto_set_curr_pos("a3");
+                                               else
+                                                       qto_set_curr_pos(minigame_relative_tile(qto_curr_pos,0,-1,QTO_NUM_CNT,QTO_LET_CNT));
+                                               return true;
+                                       case K_ENTER:
+                                       case K_KP_ENTER:
+                                       case K_SPACE:
+                                               qto_make_move(minigame);
+                                               return true;
+                               }
+                       }
+
+                       return false;
+               }
+               case "mouse_pressed":
+               {
+                       if(...(0,int) == K_MOUSE1)
+                       {
+                               qto_make_move(minigame);
+                               return true;
+                       }
+
+                       return false;
+               }
+               case "mouse_moved":
+               {
+                       vector mouse_pos = minigame_hud_normalize(mousepos,qto_boardpos,qto_boardsize);
+                       if ( minigame.minigame_flags == QTO_TURN_MOVE )
+                       {
+                               qto_set_curr_pos(minigame_tile_name(mouse_pos,QTO_NUM_CNT,QTO_LET_CNT));
+                       }
+                       if ( ! qto_valid_tile(qto_curr_pos) )
+                               qto_set_curr_pos("");
+
+                       return true;
+               }
+               case "network_receive":
+               {
+                       entity sent = ...(0,entity);
+                       int sf = ...(1,int);
+                       if ( sent.classname == "minigame" )
+                       {
+                               if ( sf & MINIG_SF_UPDATE )
+                               {
+                                       sent.message = qto_turn_to_string(sent.minigame_flags);
+                                       if ( sent.minigame_flags & minigame_self.team )
+                                               minigame_prompt();
+                               }
+                       }
+                       else if(sent.classname == "minigame_board_piece")
+                       {
+                               if(sf & MINIG_SF_UPDATE)
+                               {
+                                       sent.cnt = ReadByte();
+                               }
+                       }
+                       else if ( sent.classname == "minigame_player" && (sf & QTO_SF_PLAYERSCORE ) )
+                       {
+                               sent.qto_moves = ReadLong();
+                       }
+
+                       return false;
+               }
+               case "menu_show":
+               {
+                       HUD_MinigameMenu_CustomEntry(...(0,entity),_("Restart"),"restart");
+                       return false;
+               }
+               case "menu_click":
+               {
+                       if(...(0,string) == "restart")
+                               minigame_cmd("restart");
+                       return false;
+               }
+       }
+
+       return false;
+}
+
+#endif
\ No newline at end of file
diff --git a/qcsrc/common/minigames/minigame/ttt.qc b/qcsrc/common/minigames/minigame/ttt.qc
new file mode 100644 (file)
index 0000000..644bd8e
--- /dev/null
@@ -0,0 +1,691 @@
+const int TTT_TURN_PLACE = 0x0100; // player has to place a piece on the board
+const int TTT_TURN_WIN   = 0x0200; // player has won
+const int TTT_TURN_DRAW  = 0x0400; // no moves are possible
+const int TTT_TURN_NEXT  = 0x0800; // a player wants to start a new match
+const int TTT_TURN_TYPE  = 0x0f00; // turn type mask
+
+const int TTT_TURN_TEAM1 = 0x0001;
+const int TTT_TURN_TEAM2 = 0x0002;
+const int TTT_TURN_TEAM  = 0x000f; // turn team mask
+
+// send flags
+const int TTT_SF_PLAYERSCORE  = MINIG_SF_CUSTOM;   // send minigame_player scores (won matches)
+const int TTT_SF_SINGLEPLAYER = MINIG_SF_CUSTOM<<1;// send minigame.ttt_ai
+
+const int TTT_LET_CNT = 3;
+const int TTT_NUM_CNT = 3;
+const int TTT_TILE_SIZE = 3;
+
+.int ttt_npieces; // (minigame) number of pieces on the board (simplifies checking a draw)
+.int ttt_nexteam; // (minigame) next team (used to change the starting team on following matches)
+.int 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
+bool ttt_winning_piece(entity piece)
+{
+       int number = minigame_tile_number(piece.netname);
+       int 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 true;
+       
+       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 true;
+       
+       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 true;
+       
+       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 true;
+       
+       return false;
+}
+
+// check if the tile name is valid (3x3 grid)
+bool ttt_valid_tile(string tile)
+{
+       if ( !tile )
+               return 0;
+       int number = minigame_tile_number(tile);
+       int letter = minigame_tile_letter(tile);
+       return 0 <= number && number < TTT_NUM_CNT && 0 <= letter && letter < TTT_LET_CNT;
+}
+
+// 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 >= (TTT_LET_CNT * TTT_NUM_CNT) )
+                               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
+int ttt_server_event(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":
+               {
+                       int 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,int) == 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);
+                       int sf = ...(1,int);
+                       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
+.int ttt_checkwin; // Used to optimize checks to display a win
+
+// Required function, draw the game board
+void ttt_hud_board(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'/TTT_TILE_SIZE,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,TTT_LET_CNT,TTT_NUM_CNT);
+               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,TTT_LET_CNT,TTT_NUM_CNT);
+                       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 ttt_hud_status(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(int 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 int TTT_AI_POSFLAG_A1 = 0x0001;
+const int TTT_AI_POSFLAG_A2 = 0x0002;
+const int TTT_AI_POSFLAG_A3 = 0x0004;
+const int TTT_AI_POSFLAG_B1 = 0x0008;
+const int TTT_AI_POSFLAG_B2 = 0x0010;
+const int TTT_AI_POSFLAG_B3 = 0x0020;
+const int TTT_AI_POSFLAG_C1 = 0x0040;
+const int TTT_AI_POSFLAG_C2 = 0x0080;
+const int TTT_AI_POSFLAG_C3 = 0x0100;
+
+// convert a flag to a position
+string ttt_ai_piece_flag2pos(int 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;
+       }
+}
+
+bool ttt_ai_checkmask(int piecemask, int checkflags)
+{
+       return checkflags && (piecemask & checkflags) == checkflags;
+}
+
+// get the third flag if the mask matches two of them
+int ttt_ai_1of3(int piecemask, int flag1, int flag2, int 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
+int ttt_ai_random(int piecemask)
+{
+       if ( !piecemask )
+               return 0;
+       
+       int f = 1;
+       
+       RandomSelection_Init();
+       
+       for ( int 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
+int ttt_ai_block3 ( int piecemask, int piecemask_free )
+{
+       int 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(int piecemask_self, int piecemask_opponent, int piecemask_free )
+{
+       int 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
+                */
+               int piecemask_self = 0;
+               int piecemask_opponent = 0;
+               int piecemask_free = 0;
+               int pieceflag = 1;
+               string pos;
+               for ( int i = 0; i < 3; i++ )
+               {
+                       for ( int 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
+int ttt_client_event(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,int) )
+                               {
+                                       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,TTT_LET_CNT,TTT_NUM_CNT));
+                                               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,TTT_LET_CNT,TTT_NUM_CNT));
+                                               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,TTT_LET_CNT,TTT_NUM_CNT));
+                                               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,TTT_LET_CNT,TTT_NUM_CNT));
+                                               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,int) == 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,TTT_LET_CNT,TTT_NUM_CNT));
+                       if ( ! ttt_valid_tile(ttt_curr_pos) )
+                               ttt_set_curr_pos("");
+
+                       return true;
+               }
+               case "network_receive":
+               {
+                       entity sent = ...(0,entity);
+                       int sf = ...(1,int);
+                       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) )
+                               {
+                                       int ai = ReadByte();
+                                       bool 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 (file)
index 0000000..61ab9a6
--- /dev/null
@@ -0,0 +1,137 @@
+#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
+int 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
+int 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, int rows, int 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(int letter, int 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, int dx, int dy, int rows, int columns)
+{
+       int letter = minigame_tile_letter(start_id);
+       int 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, int rows, int columns)
+{
+       if ( pos_x < 0 || pos_x > 1 || pos_y < 0 || pos_y > 1 )
+               return ""; // no tile
+               
+       int letter = floor(pos_x * columns);
+       int 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)
+int minigame_next_team(int curr_team, int n_teams)
+{
+       return curr_team % n_teams + 1;
+}
+
+// Get the previous team number
+int minigame_prev_team(int curr_team, int 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, int 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 int msle_base_id = 2;
+int msle_id(string class_name)
+{
+       if ( class_name == "minigame" ) return 1;
+       if ( class_name == "minigame_player" ) return 2;
+       int 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(int id)
+{
+       if ( id == 1 ) return "minigame";
+       if ( id == 2 ) return "minigame_player";
+       int i = msle_base_id;
+#define MSLE(Name, Fields) i++; if ( id == i ) return #Name;
+       MINIGAME_SIMPLELINKED_ENTITIES
+#undef MSLE
+       return "";
+}
+
+int minigame_count_players(entity minigame)
+{
+       int 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;
+}
diff --git a/qcsrc/common/minigames/minigames.qh b/qcsrc/common/minigames/minigames.qh
new file mode 100644 (file)
index 0000000..86fb778
--- /dev/null
@@ -0,0 +1,127 @@
+#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
+int 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
+int minigame_tile_number(string id);
+
+// Get relative position of the center of a given tile
+vector minigame_tile_pos(string id, int rows, int columns);
+
+// Get a tile name from indices
+string minigame_tile_buildname(int letter, int number);
+
+// Get the id of a tile relative to the given one
+string minigame_relative_tile(string start_id, int dx, int dy, int rows, int columns);
+
+// Get tile name from a relative position (matches the tile covering a square area)
+string minigame_tile_name(vector pos, int rows, int columns);
+
+// Get the next team number (note: team numbers are between 1 and n_teams, inclusive)
+int minigame_next_team(int curr_team, int n_teams);
+
+// Get the previous team number
+int minigame_prev_team(int curr_team, int 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, int mgflags);
+
+// count the number of players in a minigame session
+int 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(int K_Keycode)
+///                    return 1 to handle input, 0 to discard
+///            note: see dpdefs/keycodes.qc for values
+///    key_pressed/released(int 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,int 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,int 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, int 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,int impulse)
+///            self = client entity triggering this
+///            triggered when a player does "impulse ..."
+///            return 1 if the minigame has handled the impulse
+.int(entity,string,...)   minigame_event;
+
+// For run-time gameplay entities: Whether to be removed when the game is deactivated
+.bool minigame_autoclean;
+
+// For run-time gameplay entities: some place to store flags safely
+.int 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 int MINIG_SF_CREATE  = 0x01; // Create a new object
+const int MINIG_SF_UPDATE  = 0x02; // miscellaneous entity update
+const int MINIG_SF_CUSTOM  = 0x10; // a customized networked event
+const int MINIG_SF_MAX     = 0x80; // maximum flag value sent over the network
+const int 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"
+
+int msle_id(string class_name);
+string msle_classname(int id);
+
+#endif
diff --git a/qcsrc/common/minigames/sv_minigames.qc b/qcsrc/common/minigames/sv_minigames.qc
new file mode 100644 (file)
index 0000000..157471c
--- /dev/null
@@ -0,0 +1,430 @@
+#include "minigames.qh"
+
+void player_clear_minigame(entity player)
+{
+       player.active_minigame = world;
+       player.minigame_players = 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 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
+bool minigame_SendEntity(entity to, int 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 true;
+       
+}
+#undef FIELD
+#undef MSLE
+
+// 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;
+       }
+}
+
+bool minigame_CheckSend()
+{
+       entity e;
+       for ( e = self.owner.minigame_players; e != world; e = e.list_next )
+               if ( e.minigame_players == other )
+                       return true;
+       return false;
+}
+
+int 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);
+       }
+       
+       int 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.minigame_players = player_pointer;
+               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 = name##_server_event; \
+               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;
+}
+
+bool MinigameImpulse(int 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 false;
+}
+
+
+
+void ClientCommand_minigame(int request, int 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);
+                       int 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 <minigame>\n");
+       sprint(self, "  Start a new minigame session\n");
+       sprint(self, "Usage:^3 cmd minigame join <session>\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 <player>\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 (file)
index 0000000..246440d
--- /dev/null
@@ -0,0 +1,52 @@
+#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
+bool minigame_CheckSend();
+
+// Check for minigame impulses
+bool MinigameImpulse(int imp);
+
+// Parse a client command ( cmd minigame ... )
+void ClientCommand_minigame(int request, int 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;
+
+bool minigame_SendEntity(entity to, int sf);
+
+#endif
index a406df52419dc71d1bdf985e7aa963e83b988fa1..31f7e864c0ac4a5579dff63d86a9ec49b6d3f205 100644 (file)
@@ -1561,6 +1561,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,
index 92386b313249c023f3d0e9b70ce2c633b14c697c..cc7b397a49ad3d63855336fd89b1667bebfe9bbe 100644 (file)
@@ -513,6 +513,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"), "") \
@@ -769,6 +770,7 @@ 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_JOIN_PREVENT_MINIGAME,       0, 0, "",              NO_CPID,               "0 0",  _("^K1Cannot join given minigame session!"), "" ) \
     MSG_CENTER_NOTIF(1, CENTER_VEHICLE_ENTER,               0, 0, "pass_key",      CPID_VEHICLES,         "0 0",  _("^BGPress ^F2DROPFLAG%s^BG to enter/exit the vehicle"), "") \
     MSG_CENTER_NOTIF(1, CENTER_VEHICLE_ENTER_GUNNER,        0, 0, "pass_key",      CPID_VEHICLES,         "0 0",  _("^BGPress ^F2DROPFLAG%s^BG to enter the vehicle gunner"), "") \
     MSG_CENTER_NOTIF(1, CENTER_VEHICLE_ENTER_STEAL,         0, 0, "pass_key",      CPID_VEHICLES,         "0 0",  _("^BGPress ^F2DROPFLAG%s^BG to steal this vehicle"), "") \
@@ -1060,6 +1062,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;
@@ -1116,7 +1120,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; } \
@@ -1511,6 +1517,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) \
+    int 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; \
index 9d6c49d2f0747164b1f95424944ab265965d6896..cf1d1fc16c40293b830df0b46dec7b4ab0073f5d 100644 (file)
@@ -194,7 +194,6 @@ bool autocvar_g_ban_sync_trusted_servers_verify;
 string autocvar_g_ban_sync_uri;
 string autocvar_g_banned_list;
 bool autocvar_g_banned_list_idmode;
-bool autocvar_g_bastet;
 bool autocvar_g_botclip_collisions;
 bool autocvar_g_bugrigs;
 float autocvar_g_ca_damage2score_multiplier;
@@ -855,6 +854,8 @@ 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;
 float autocvar_g_buffs_inferno_burntime_factor;
 float autocvar_g_buffs_inferno_burntime_min_time;
 float autocvar_g_buffs_inferno_burntime_target_damage;
index 9a832efd4d67449fdb99f533603e427adf0356ac..ee3fc76ffb693fdee0d3d926f4e4e7ea746697a0 100644 (file)
@@ -39,6 +39,8 @@
 #include "../common/triggers/triggers.qh"
 #include "../common/triggers/trigger/secret.qh"
 
+#include "../common/minigames/sv_minigames.qh"
+
 #include "../common/items/inventory.qh"
 
 #include "../common/monsters/sv_monsters.qh"
@@ -1298,6 +1300,9 @@ void ClientDisconnect (void)
 
        PlayerStats_GameReport_FinalizePlayer(self);
 
+       if ( self.active_minigame )
+               part_minigame(self);
+
        if(IS_PLAYER(self)) { Send_Effect("spawn_event_neutral", self.origin, '0 0 0', 1); }
 
        CheatShutdownClient();
@@ -1376,14 +1381,20 @@ void ChatBubbleThink()
                remove(self);
                return;
        }
-       if ((self.owner.BUTTON_CHAT && !self.owner.deadflag)
-#ifdef TETRIS
-               || self.owner.tetris_on
-#endif
-       )
-               self.model = self.mdl;
-       else
-               self.model = "";
+       
+       self.mdl = "";
+       
+       if ( !self.owner.deadflag && IS_PLAYER(self.owner) )
+       {
+               if ( self.owner.active_minigame )
+                       self.mdl = "models/sprites/minigame_busy.iqm";
+               else if ( self.owner.BUTTON_CHAT )
+                       self.mdl = "models/misc/chatbubble.spr";
+       }
+       
+       if ( self.model != self.mdl )
+               setmodel(self, self.mdl);
+
 }
 
 void UpdateChatBubble()
@@ -1400,10 +1411,10 @@ void UpdateChatBubble()
                self.chatbubbleentity.nextthink = time;
                setmodel(self.chatbubbleentity, "models/misc/chatbubble.spr"); // precision set below
                //setorigin(self.chatbubbleentity, self.origin + '0 0 15' + self.maxs_z * '0 0 1');
-               setorigin(self.chatbubbleentity, '0 0 15' + self.maxs.z * '0 0 1');
+               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;
        }
 }
@@ -2086,6 +2097,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) {
@@ -2116,6 +2132,11 @@ void ObserverThink()
 
 void SpectatorThink()
 {
+       if ( self.impulse )
+       {
+               if(MinigameImpulse(self.impulse))
+                       self.impulse = 0;
+       }
        if (self.flags & FL_JUMPRELEASED) {
                if (self.BUTTON_JUMP && !self.version_mismatch) {
                        self.flags &= ~FL_JUMPRELEASED;
@@ -2333,11 +2354,6 @@ void PlayerPreThink (void)
                self.max_armorvalue = 0;
        }
 
-#ifdef TETRIS
-       if (TetrisPreFrame())
-               return;
-#endif
-
        if(self.frozen == 2)
        {
                self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1);
@@ -2683,13 +2699,6 @@ void PlayerPostThink (void)
                }
        }
 
-#ifdef TETRIS
-       if(self.impulse == 100)
-               ImpulseCommands();
-       if (!TetrisPostFrame())
-       {
-#endif
-
        CheatFrame();
 
        //CheckPlayerJump();
@@ -2704,10 +2713,6 @@ void PlayerPostThink (void)
                GetPressedKeys();
        }
 
-#ifdef TETRIS
-       }
-#endif
-
        /*
        float i;
        for(i = 0; i < 1000; ++i)
index 45708409c5fe9731e95088dee3fcb9779b15daa1..64fb50df83a9db913302bc7ee4fd2d869d0c9b4c 100644 (file)
@@ -12,6 +12,8 @@
 #include "weapons/weaponsystem.qh"
 #include "waypointsprites.qh"
 
+#include "../common/minigames/sv_minigames.qh"
+
 #include "../common/weapons/all.qh"
 
 /*
@@ -62,6 +64,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)
@@ -404,8 +410,4 @@ void ImpulseCommands (void)
                        }
                }
        }
-#ifdef TETRIS
-       else if(imp == 100)
-               TetrisImpulse();
-#endif
 }
index e3bb91e3954bfe112009a2d0a28d2549797993f0..46d1b2cbd0ba08c81b08d4ce4a606fc8309d01d6 100644 (file)
@@ -19,6 +19,8 @@
 #include "../common/playerstats.qh"
 #include "../csqcmodellib/sv_model.qh"
 
+#include "../common/minigames/sv_minigames.qh"
+
 #include "weapons/weaponstats.qh"
 
 #include "../common/animdecide.qh"
@@ -869,6 +871,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);
index c6d122eed3e4b4bb5632e309681857d24c1bbf9b..24187b172f01a398cc8b7f6bfda135d63c62e234 100644 (file)
@@ -820,6 +820,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()
index 0fc713f69ab2d4e7f92a43b4c648d487bf4dd5b9..d743a2cba0027eac2242779e6c69e7b4fec504bb 100644 (file)
@@ -613,6 +613,8 @@ void spawnfunc_worldspawn (void)
        CALL_ACCUMULATED_FUNCTION(RegisterDeathtypes);
        CALL_ACCUMULATED_FUNCTION(RegisterEffects);
 
+       initialize_minigames();
+
        ServerProgsDB = db_load(strcat("server.db", autocvar_sessionid));
 
        TemporaryDB = db_create();
index 3d39893d06401c9989255ba5935ac317c215f259..f10b721ae5ec081b08e2972d808f3acd32533c57 100644 (file)
@@ -23,7 +23,6 @@ g_hook.qc
 // g_lights.qc // TODO: was never used
 g_models.qc
 g_subs.qc
-g_tetris.qc
 g_violence.qc
 g_world.qc
 generator.qc
@@ -90,6 +89,8 @@ weapons/weaponsystem.qc
 ../common/mapinfo.qc
 ../common/monsters/spawn.qc
 ../common/monsters/sv_monsters.qc
+../common/minigames/minigames.qc
+../common/minigames/sv_minigames.qc
 ../common/movetypes/include.qc
 ../common/nades.qc
 ../common/net_notice.qc
diff --git a/scripts/minigames.shader b/scripts/minigames.shader
new file mode 100644 (file)
index 0000000..053e6fc
--- /dev/null
@@ -0,0 +1,11 @@
+minigame_busy
+{
+       deformVertexes autosprite
+       cull none
+       nopicmip
+
+       {
+               map models/sprites/minigame_busy
+               blendfunc blend
+       }
+}