From 791f83c59a45012c02a2cc9e03ad0afc12d4804f Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Fri, 22 May 2026 04:24:02 -0400 Subject: [PATCH 01/36] stuff --- code/__DEFINES/~doppler_defines/wargames.dm | 28 ++++ .../wargaming/code/base_station.dm | 153 ++++++++++++++++++ modular_doppler/wargaming/code/controller.dm | 3 + modular_doppler/wargaming/code/holograms.dm | 43 ++++- modular_doppler/wargaming/code/projectors.dm | 31 +--- modular_doppler/wargaming/code/team_datums.dm | 10 ++ modular_doppler/wargaming/icons/actions.dmi | Bin 0 -> 1756 bytes modular_doppler/wargaming/icons/items.dmi | Bin 0 -> 478 bytes .../wargaming/icons/mob/lefthand.dmi | Bin 0 -> 405 bytes .../wargaming/icons/mob/righthand.dmi | Bin 0 -> 395 bytes modular_doppler/wargaming/icons/mob/worn.dmi | Bin 0 -> 654 bytes tgstation.dme | 3 + 12 files changed, 244 insertions(+), 27 deletions(-) create mode 100644 code/__DEFINES/~doppler_defines/wargames.dm create mode 100644 modular_doppler/wargaming/code/base_station.dm create mode 100644 modular_doppler/wargaming/code/controller.dm create mode 100644 modular_doppler/wargaming/code/team_datums.dm create mode 100644 modular_doppler/wargaming/icons/actions.dmi create mode 100644 modular_doppler/wargaming/icons/items.dmi create mode 100644 modular_doppler/wargaming/icons/mob/lefthand.dmi create mode 100644 modular_doppler/wargaming/icons/mob/righthand.dmi create mode 100644 modular_doppler/wargaming/icons/mob/worn.dmi diff --git a/code/__DEFINES/~doppler_defines/wargames.dm b/code/__DEFINES/~doppler_defines/wargames.dm new file mode 100644 index 00000000000000..1120902f441e65 --- /dev/null +++ b/code/__DEFINES/~doppler_defines/wargames.dm @@ -0,0 +1,28 @@ +/// Current game phase is placing units +#define WARGAME_PHASE_PLACEMENT 0 +/// Current game phase is pre-turn updates +#define WARGAME_PHASE_UPDATES 1 +/// Current game phase is action +#define WARGAME_PHASE_ACTION 2 +/// Current game phase is effects +#define WARGAME_PHASE_EFFECTS 3 +/// Current game phase is nothing, the game is over or hasn't even started yet +#define WARGAME_PHASE_NOTHING 10 + +/// Add a team to the base station +#define BASESTATION_ADD_TEAM "Add Team" +/// Remove a team from the base station +#define BASESTATION_REMOVE_TEAM "Remove Team" +/// Edit a team on the base station +#define BASESTATION_EDIT_TEAM "Edit Team" +/// Reset the basestation, removing all teams and configuration +#define BASESTATION_RESET "Reset Configuration" +/// Start the game with all current base station settings +#define BASESTATION_START "Start Game" +/// Immediately stop the currently running game on the base station +#define BASESTATION_END "End Game" + +/// Change the name of the currently selected team +#define BASESTATION_TEAM_NAME "Team Name" +/// Change the color of the currently selected team +#define BASESTATION_TEAM_COLOR "Team Color" diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm new file mode 100644 index 00000000000000..c6e3ea1baf51d3 --- /dev/null +++ b/modular_doppler/wargaming/code/base_station.dm @@ -0,0 +1,153 @@ +#define ACTIONS_FILE 'modular_doppler/wargaming/icons/actions.dmi' + +/obj/item/wargame_base_station + name = "wargames base station" + desc = "A base station for holographic wargames, all controllers and interactive wearables link to this machine and are automatically managed by it." + icon = 'modular_doppler/wargaming/icons/items.dmi' + icon_state = "basestation" + lefthand_file = 'modular_doppler/wargaming/icons/mob/lefthand.dmi' + righthand_file = 'modular_doppler/wargaming/icons/mob/righthand.dmi' + inhand_icon_state = "generic" + w_class = WEIGHT_CLASS_SMALL + item_flags = NOBLUDGEON + /// List of all team datums this controller is currently managing, empty teams are cleared at game start + var/list/managed_teams = list() + /// What phase of the game are we currently in + var/game_phase = WARGAME_PHASE_NOTHING + /// Radial options for before the game has been started + var/static/list/pre_game_radial_options = list( + BASESTATION_ADD_TEAM = image(icon = ACTIONS_FILE, icon_state = "add_team"), + BASESTATION_REMOVE_TEAM = image(icon = ACTIONS_FILE, icon_state = "delete_team"), + BASESTATION_EDIT_TEAM = image(icon = ACTIONS_FILE, icon_state = "edit_team"), + BASESTATION_RESET = image(icon = ACTIONS_FILE, icon_state = "reset"), + BASESTATION_START = image(icon = ACTIONS_FILE, icon_state = "start_game"), + ) + /// Radial options for after the game has already been started + var/static/list/mid_game_radial_options = list( + BASESTATION_END = image(icon = ACTIONS_FILE, icon_state = "end_game"), + ) + /// Radial options for editing a team + var/static/list/team_edit_radial_options = list( + BASESTATION_TEAM_NAME = image(icon = ACTIONS_FILE, icon_state = "team_name"), + BASESTATION_TEAM_COLOR = image(icon = ACTIONS_FILE, icon_state = "team_color"), + ) + +/obj/item/wargame_base_station/click_alt(mob/living/user) + var/picked_choice = show_radial_menu(user, src, (game_phase == WARGAME_PHASE_NOTHING) ? pre_game_radial_options : mid_game_radial_options, require_near = TRUE, tooltips = TRUE) + if(isnull(picked_choice)) + return CLICK_ACTION_BLOCKING + switch(picked_choice) + if(BASESTATION_ADD_TEAM) + create_new_team(user) + if(BASESTATION_REMOVE_TEAM) + delete_team(user) + if(BASESTATION_EDIT_TEAM) + edit_a_team(user) + if(BASESTATION_RESET) + reset_everything(user) + if(BASESTATION_START) + start_the_game(user) + if(BASESTATION_END) + end_the_game(user) + return CLICK_ACTION_SUCCESS + +#define MY_CHILD_WILL "Yes" +#define MY_CHILD_WILL_NOT "No" + +/// Ends the game if it is currently running +/obj/item/wargame_base_station/proc/end_the_game(mob/living/user) + if(game_phase == WARGAME_PHASE_NOTHING) + balloon_alert(user, "already stopped!") + return + var/certainty = tgui_alert(user, "Are you certain you wish to end the game?", "Game Ender", list(MY_CHILD_WILL, MY_CHILD_WILL_NOT)) + if(certainty != MY_CHILD_WILL) + return + game_phase = WARGAME_PHASE_NOTHING + +/// Starts the game if it has not already been started +/obj/item/wargame_base_station/proc/start_the_game(mob/living/user) + if(game_phase != WARGAME_PHASE_NOTHING) + balloon_alert(user, "already started!") + return + if(length(managed_teams) <= 1) + balloon_alert(user, "too few teams!") + return + var/certainty = tgui_alert(user, "Are you certain you wish to start the game?", "Game Starter", list(MY_CHILD_WILL, MY_CHILD_WILL_NOT)) + if(certainty != MY_CHILD_WILL) + return + game_phase = WARGAME_PHASE_PLACEMENT + +/// Asks for confirmation before deleting every team +/obj/item/wargame_base_station/proc/reset_everything(mob/living/user) + var/certainty = tgui_alert(user, "Are you certain you wish to delete every team?", "Deletion Confirmation", list(MY_CHILD_WILL, MY_CHILD_WILL_NOT)) + if(certainty == MY_CHILD_WILL) + if(var/team as anything in managed_teams) + qdel(managed_teams[team]) + managed_teams -= team + +/// Selects a team and edits the name or color +/obj/item/wargame_base_station/proc/edit_a_team(mob/living/user) + var/editing_team = tgui_input_list(user, "Choose which team you wish to edit.", "Team List", managed_teams) + if(isnull(editing_team)) + balloon_alert(user, "no choice!") + return + show_team_edit_radial(user, managed_teams[editing_team]) + +/// Constantly shows a radial for editing a team until cancelled +/obj/item/wargame_base_station/proc/show_team_edit_radial(mob/living/user, datum/wargaming_team/edited_team) + var/picked_choice = show_radial_menu(user, src, team_edit_radial_options, require_near = TRUE, tooltips = TRUE) + if(isnull(picked_choice)) + return + switch(picked_choice) + if(BASESTATION_TEAM_NAME) + var/new_name = tgui_input_text(user, "Give your new team a name.", "Team Name", "Unnamed Combatants") + if(isnull(new_name)) + balloon_alert(user, "needs name!") + continue + if(new_name in managed_teams) + balloon_alert(user, "name in use!") + continue + edited_team.team_name = new_name + if(BASESTATION_TEAM_COLOR) + var/new_color = input(user, "Choose your new team color." ,"Color Selection", COLOR_PRIDE_PURPLE) as color|null + if(isnull(new_color)) + balloon_alert(user, "needs color!") + continue + edited_team.team_color = new_color + show_team_edit_radial(user, edited_team) + +/// Lets players pick from a list of existing teams to delete one +/obj/item/wargame_base_station/proc/delete_team(mob/living/user) + var/deleting_team = tgui_input_list(user, "Choose which team you wish to delete.", "Team List", managed_teams) + if(isnull(deleting_team)) + balloon_alert(user, "no choice!") + return + var/certainty = tgui_alert(user, "Are you certain you wish to delete [deleting_team]?", "Deletion Confirmation", list(MY_CHILD_WILL, MY_CHILD_WILL_NOT)) + if(certainty == MY_CHILD_WILL) + qdel(managed_teams[deleting_team]) + managed_teams -= deleting_team + +/// Interactively create a new team with the user +/obj/item/wargame_base_station/proc/create_new_team(mob/living/user) + var/datum/wargaming_team/new_team = new() + var/new_name = tgui_input_text(user, "Give your new team a name.", "Team Name", "Unnamed Combatants") + if(isnull(new_name)) // How? + qdel(new_team) + balloon_alert(user, "needs name!") + return + if(new_name in managed_teams) // This name is already in use, get some new material + qdel(new_team) + balloon_alert(user, "name in use!") + return + new_team.team_name = new_name + var/new_color = input(user, "Choose your new team color." ,"Color Selection", COLOR_PRIDE_PURPLE) as color|null + if(isnull(new_color)) + qdel(new_team) + balloon_alert(user, "needs color!") + return + new_team.team_color = new_color + managed_teams[new_team.team_name] += new_team + +#undef MY_CHILD_WILL +#undef MY_CHILD_WILL_NOT +#undef ACTIONS_FILE diff --git a/modular_doppler/wargaming/code/controller.dm b/modular_doppler/wargaming/code/controller.dm new file mode 100644 index 00000000000000..ac07ee08fecdeb --- /dev/null +++ b/modular_doppler/wargaming/code/controller.dm @@ -0,0 +1,3 @@ +/obj/item/wargame_controller + name = "wargaming controller" + desc = "A handheld controller for controlling holograms. Must be linked to a base station in order to work." diff --git a/modular_doppler/wargaming/code/holograms.dm b/modular_doppler/wargaming/code/holograms.dm index 1d85021ef680c9..e12b121a81cd58 100644 --- a/modular_doppler/wargaming/code/holograms.dm +++ b/modular_doppler/wargaming/code/holograms.dm @@ -1,20 +1,23 @@ /obj/structure/wargame_hologram - name = "broken holographic wargame marker" - desc = "You have a feeling like this is supposed to be telling you something, but the hologram must have broken." + abstract_type = /obj/structure/wargame_hologram icon = 'modular_doppler/wargaming/icons/projectors_and_holograms.dmi' icon_state = null anchored = TRUE density = FALSE - max_integrity = 1 + max_integrity = 30 obj_flags = UNIQUE_RENAME /// What object created this projection? Can be null as a projector isn't required for this to exist var/obj/item/wargame_projector/projector + /// If this hologram ignores pixel shifting when placed, instead using swarming + var/swarming = FALSE /obj/structure/wargame_hologram/Initialize(mapload, source_projector) . = ..() if(source_projector) projector = source_projector LAZYADD(projector.projections, src) + if(swarming) + AddComponent(/datum/component/swarming) /obj/structure/wargame_hologram/Destroy() if(projector) @@ -26,6 +29,40 @@ . = ..() . += emissive_appearance(icon, icon_state, src) +/obj/structure/wargame_hologram/attack_hand(mob/living/user, list/modifiers) + if(user.combat_mode) + return ..() + hologram_controls(user) + +/// Handles controlling the hologram when clicked on +/obj/structure/wargame_hologram/proc/hologram_controls(mob/living/user) + return + +/obj/structure/wargame_hologram/controllable + abstract_type = /obj/structure/wargame_hologram/controllable + /// The team datum responsible for managing this hologram, if any + var/datum/wargaming_team/owner_team + /// Does this controllable thing require a mob interacting with it to be on its team + var/requires_same_team = TRUE + +/obj/structure/wargame_hologram/controllable/hologram_controls(mob/living/user) + SHOULD_CALL_PARENT(TRUE) + if(requires_same_team && !verify_user_team(user)) + balloon_alert(user, "wrong team!") + return + +/// Checks if the user is in the team datum's players list +/obj/structure/wargame_hologram/controllable/proc/verify_user_team(mob/living/user) + if(!owner_team) + return FALSE + if(user in owner_team.team_players) + return TRUE + return FALSE + +/obj/structure/wargame_hologram/controllable/ship + abstract_type = /obj/structure/wargame_hologram/controllable/ship + swarming = TRUE + /// Projections for 'moving vessels' in order from smallest to largest representation /obj/structure/wargame_hologram/strike_craft diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index 18729d3d1913a7..ec4f18ba2b5832 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -1,23 +1,18 @@ /obj/item/wargame_projector - name = "holographic projector" - desc = "A handy-dandy holographic projector developed by the Port Authority Naval Command for playing wargames with, this one seems broken." + name = "holographic wargame projector" + desc = "A holographic projectors for creating holograms that work in the wargaming system." icon = 'modular_doppler/wargaming/icons/projectors_and_holograms.dmi' icon_state = "projector" base_icon_state = "projector" - inhand_icon_state = "electronic" - worn_icon_state = "electronic" - lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - force = 0 + lefthand_file = 'modular_doppler/wargaming/icons/mob/lefthand.dmi' + righthand_file = 'modular_doppler/wargaming/icons/mob/righthand.dmi' + inhand_icon_state = "generic" + worn_icon = 'modular_doppler/wargaming/icons/mob/worn.dmi' + worn_icon_state = "generic" w_class = WEIGHT_CLASS_SMALL - throwforce = 0 - throw_speed = 3 - throw_range = 7 item_flags = NOBLUDGEON /// All of the signs this projector is maintaining var/list/projections - /// The maximum number of projections this can support - var/max_signs = 1 /// The color to give holograms when created var/holosign_color = COLOR_WHITE /// The type of hologram to spawn on click @@ -46,17 +41,12 @@ /obj/item/wargame_projector/Initialize(mapload) . = ..() - AddElement(/datum/element/openspace_item_click_handler) update_appearance() populate_radial_choice_lists() -/obj/item/wargame_projector/handle_openspace_click(turf/target, mob/user, click_parameters) - ranged_interact_with_atom(target, user, params2list(click_parameters)) - /obj/item/wargame_projector/update_appearance() . = ..() cut_overlays() - var/image/color_select_overlay = image(icon = icon, icon_state = "[base_icon_state]_screen") color_select_overlay.color = holosign_color add_overlay(color_select_overlay) @@ -84,10 +74,8 @@ require_near = TRUE, tooltips = TRUE, ) - if(isnull(picked_choice)) return - holosign_type = projection_names_to_path[picked_choice] /obj/item/wargame_projector/attack_self(mob/user) @@ -125,19 +113,14 @@ /// Spawn a hologram with pixel offset based on where the user clicked /obj/item/wargame_projector/proc/create_hologram(atom/target, mob/user, list/modifiers) var/obj/target_holosign = new holosign_type(get_turf(target), src) - var/click_x var/click_y - if(LAZYACCESS(modifiers, ICON_X) && LAZYACCESS(modifiers, ICON_Y)) click_x = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(world.icon_size/2), world.icon_size/2) click_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(world.icon_size/2), world.icon_size/2) - target_holosign.pixel_x = click_x target_holosign.pixel_y = click_y - target_holosign.color = holosign_color - playsound(loc, 'sound/machines/click.ogg', 20, TRUE) /obj/item/wargame_projector/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) diff --git a/modular_doppler/wargaming/code/team_datums.dm b/modular_doppler/wargaming/code/team_datums.dm new file mode 100644 index 00000000000000..926237e113e232 --- /dev/null +++ b/modular_doppler/wargaming/code/team_datums.dm @@ -0,0 +1,10 @@ +/datum/wargaming_team + abstract_type = /datum/wargaming_team + /// The name of the team as displayed on the controllers + var/team_name + /// The color of the team as shown in holograms and outfits + var/team_color = "#fff" + /// The players currently on this team + var/list/team_players = list() + /// Associated list of equipment to the player that linked it + var/list/players_equipment = list() diff --git a/modular_doppler/wargaming/icons/actions.dmi b/modular_doppler/wargaming/icons/actions.dmi new file mode 100644 index 0000000000000000000000000000000000000000..29a937b40080459d878f2231dd4bb306085f6c51 GIT binary patch literal 1756 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 S*xkPfa zg2M`mO22;zF8KKMiI%sn*10q1gExd4Tr__0Nawtd=1GR4p57f6#zDrHjlC4+JeqW5 zQiwvZrmd;|Rt@9G&TeDX%_}69^mZGs)i5*{J!1AGD6HUXF{3mOLxro<>xZ((t^n=5 z<>}%WQZeW4O-HUn1|qH(Uzj`=n&bK6-}MQ;sWCr27A)S9nH{&{dxJvA?bFPT9mPN` zAkeU0;84)L;}X+vrMqu@7xP^{$$W$Kt=C`9T4m0p_lOrV-{FR!!`Yws(*G3&lul4**a(cf29@M58F5Heg1xNw%mif=kH~DJm%Xm e!1TR+&;ElU#Y%(6F(AAZ zg2M`mO22;zF8KKMiI%sn*10q1gExd4Tr__0Nawtd=1GR4p57f6#zDrHjlC4+JeqW5 zQiwvZrltM*B^vhX>RUZJyNy*huaHoCU|_u9>EaktG3V{=jb29_1de^wC|jv|!}EhIpP){y-rB9#qBeYE zTyv=O+crD5S106TWSnNDl?Myys)JAoupN{rfEUJfCvyd~CAj%X@p|_+K_iYW{rpsf#b* zT`!+Tc|I`IL14|w^zX_>e|>js+v&@{jPZ(@)w{-(9EMB+$_$48IJ+-0np*g=EV)^$ z=Wk&xd-TDlmd*d$V_!Y(%qqOWs?bte!PT{DN#uzW--X4>8)~F}98r6(KIIx?;PTC@ zUpAb*>#wnrdD045uuTWH%~@%_z>KGPi=F+64#5TqMuNnQNQQgGleeD9jEm5HVrv?6 zX38h8G{&CC8Cydpel1$aeB=d#;WDKeT{q0Y?nNaw+J|LLywJc9dkbW}r>mdKI;Vst E0A&mn!2kdN literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index dd28a59c785235..658436c0356d00 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -474,6 +474,7 @@ #include "code\__DEFINES\~doppler_defines\traits.dm" #include "code\__DEFINES\~doppler_defines\vehicles.dm" #include "code\__DEFINES\~doppler_defines\vv.dm" +#include "code\__DEFINES\~doppler_defines\wargames.dm" #include "code\__DEFINES\~doppler_defines\wounds.dm" #include "code\__DEFINES\~doppler_defines\traits\declarations.dm" #include "code\__HELPERS\_auxtools_api.dm" @@ -7953,9 +7954,11 @@ #include "modular_doppler\verbs\code\preferences.dm" #include "modular_doppler\verbs\code\say.dm" #include "modular_doppler\verbs\code\subtle.dm" +#include "modular_doppler\wargaming\code\base_station.dm" #include "modular_doppler\wargaming\code\game_kit.dm" #include "modular_doppler\wargaming\code\holograms.dm" #include "modular_doppler\wargaming\code\projectors.dm" +#include "modular_doppler\wargaming\code\team_datums.dm" #include "modular_doppler\z_lore_rename\pa_4ca.dm" #include "modular_doppler\z_lore_rename\syndicate_removal.dm" // END_INCLUDE From 0980919b1ae7a90f2314e72306c3dcd258cd5e65 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Fri, 22 May 2026 11:59:28 -0400 Subject: [PATCH 02/36] big nuke --- code/__DEFINES/~doppler_defines/wargames.dm | 10 +- .../wargaming/code/base_station.dm | 170 +++++++++++++++++- modular_doppler/wargaming/code/holograms.dm | 40 ++--- modular_doppler/wargaming/code/projectors.dm | 59 ++++-- modular_doppler/wargaming/icons/actions.dmi | Bin 1756 -> 2127 bytes 5 files changed, 234 insertions(+), 45 deletions(-) diff --git a/code/__DEFINES/~doppler_defines/wargames.dm b/code/__DEFINES/~doppler_defines/wargames.dm index 1120902f441e65..a80a4d93548894 100644 --- a/code/__DEFINES/~doppler_defines/wargames.dm +++ b/code/__DEFINES/~doppler_defines/wargames.dm @@ -1,11 +1,9 @@ /// Current game phase is placing units #define WARGAME_PHASE_PLACEMENT 0 -/// Current game phase is pre-turn updates -#define WARGAME_PHASE_UPDATES 1 /// Current game phase is action -#define WARGAME_PHASE_ACTION 2 +#define WARGAME_PHASE_ACTION 1 /// Current game phase is effects -#define WARGAME_PHASE_EFFECTS 3 +#define WARGAME_PHASE_EFFECTS 2 /// Current game phase is nothing, the game is over or hasn't even started yet #define WARGAME_PHASE_NOTHING 10 @@ -15,12 +13,16 @@ #define BASESTATION_REMOVE_TEAM "Remove Team" /// Edit a team on the base station #define BASESTATION_EDIT_TEAM "Edit Team" +/// Allows the user to join or leave a team +#define BASESTATION_JOIN_LEAVE "Join/Leave Team" /// Reset the basestation, removing all teams and configuration #define BASESTATION_RESET "Reset Configuration" /// Start the game with all current base station settings #define BASESTATION_START "Start Game" /// Immediately stop the currently running game on the base station #define BASESTATION_END "End Game" +/// End the current phase and advance to the next, changing to the next team's turn if applicable +#define BASESTATION_NEXT "Next Phase" /// Change the name of the currently selected team #define BASESTATION_TEAM_NAME "Team Name" diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index c6e3ea1baf51d3..9fc7893dbc2f23 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -12,10 +12,17 @@ item_flags = NOBLUDGEON /// List of all team datums this controller is currently managing, empty teams are cleared at game start var/list/managed_teams = list() + /// Nebulous list of teams per round, subtracted from as teams complete their turns + var/list/teams_per_turn = list() /// What phase of the game are we currently in var/game_phase = WARGAME_PHASE_NOTHING + /// Which team's turn is it right now? + var/datum/wargaming_team/team_turn + /// How many turns have passed + var/turn_counter = 1 /// Radial options for before the game has been started var/static/list/pre_game_radial_options = list( + BASESTATION_JOIN_LEAVE = image(icon = ACTIONS_FILE, icon_state = "join_team"), BASESTATION_ADD_TEAM = image(icon = ACTIONS_FILE, icon_state = "add_team"), BASESTATION_REMOVE_TEAM = image(icon = ACTIONS_FILE, icon_state = "delete_team"), BASESTATION_EDIT_TEAM = image(icon = ACTIONS_FILE, icon_state = "edit_team"), @@ -25,6 +32,7 @@ /// Radial options for after the game has already been started var/static/list/mid_game_radial_options = list( BASESTATION_END = image(icon = ACTIONS_FILE, icon_state = "end_game"), + BASESTATION_NEXT = image(icon = ACTIONS_FILE, icon_state = "next_phase"), ) /// Radial options for editing a team var/static/list/team_edit_radial_options = list( @@ -32,11 +40,35 @@ BASESTATION_TEAM_COLOR = image(icon = ACTIONS_FILE, icon_state = "team_color"), ) +/obj/item/wargame_base_station/examine(mob/user) + . = ..() + . += span_notice("Interact with the menu with [EXAMINE_HINT("Alt-Click")].") + // Game stats + var/game_stats_text + game_stats_text = "The game is currently in the [game_phase_2_text(game_phase)] phase.
" + game_stats_text += game_phase_2_desc(game_phase) + game_stats_text += "The teams have completed [EXAMINE_HINT("[turn_counter - 1]")] total turns.
" + . += fieldset_block(game_phase_2_text(game_phase, TRUE), game_stats_text, "boxed_message") + // Player and team stats + for(var/team as anything in managed_teams) + var/datum/wargaming_team/team_datum = managed_teams[team] + var/team_display + if(length(team_datum.team_players)) + team_display = "Composed of [length(team_datum.team_players)] players:
" + for(var/mob/living/player_on_team as anything in team_datum.team_players) + team_display += " - [player_on_team.get_visible_name()]
" + else + team_display = "Team currently empty." + . += fieldset_block(span_bold(team), team_display, "boxed_message") + /obj/item/wargame_base_station/click_alt(mob/living/user) var/picked_choice = show_radial_menu(user, src, (game_phase == WARGAME_PHASE_NOTHING) ? pre_game_radial_options : mid_game_radial_options, require_near = TRUE, tooltips = TRUE) if(isnull(picked_choice)) return CLICK_ACTION_BLOCKING switch(picked_choice) + // Pregame options + if(BASESTATION_JOIN_LEAVE) + try_join_leave(user) if(BASESTATION_ADD_TEAM) create_new_team(user) if(BASESTATION_REMOVE_TEAM) @@ -47,13 +79,113 @@ reset_everything(user) if(BASESTATION_START) start_the_game(user) + // Midgame options if(BASESTATION_END) end_the_game(user) + if(BASESTATION_NEXT) + advance_phase(user) return CLICK_ACTION_SUCCESS +/// Converts a given game phase number to text +/obj/item/wargame_base_station/proc/game_phase_2_text(phase, capital) + switch(phase) + if(WARGAME_PHASE_PLACEMENT) + return capital ? "Placement" : "placement" + if(WARGAME_PHASE_ACTION) + return capital ? "Action" : "action" + if(WARGAME_PHASE_EFFECTS) + return capital ? "Effects" : "effects" + if(WARGAME_PHASE_NOTHING) + return capital ? "Pre-Game" : "pre-game" + +/// Converts a given game phase number to a description of it +/obj/item/wargame_base_station/proc/game_phase_2_desc(phase) + switch(phase) + if(WARGAME_PHASE_PLACEMENT) + return "All teams should use their hologram projectors to create their fleets during this phase, this will be the only opportunity \ + to use the projectors. If battlespace terrain and hazards are desired, the tertiary \"Terrain\" projector should be linked and \ + used instead.
" + if(WARGAME_PHASE_ACTION) + return "All units on the team who's turn it currently is are allowed to use their action points to move, attack, etc. Events such as \ + ship destruction will not take place until the next phase.
" + if(WARGAME_PHASE_EFFECTS) + return "In this phase, effects such as ships exploding due to damage are processed. This may take up to a few seconds per event, and \ + control to skip to the next phase will be locked until all effects have been handled.
" + if(WARGAME_PHASE_NOTHING) + return "The game is currently in the pre-game lobby, nothing will happen until the \"Start Game\" option has been chosen in the menu. \ + In order to start, the game needs at least two teams with at least one person each. These do not need to be different people, allowing \ + for solo play.
" + #define MY_CHILD_WILL "Yes" #define MY_CHILD_WILL_NOT "No" +#define JOIN_A_TEAM "Join" +#define LEAVE_A_TEAM "Leave" + +/// Allows the user to join or leave a team of their choosing +/obj/item/wargame_base_station/proc/try_join_leave(mob/living/user) + if(game_phase != WARGAME_PHASE_NOTHING) + balloon_alert(user, "already in progress!") + return + var/option = tgui_alert(user, "Join or leave a team?", "Team Manager", list(JOIN_A_TEAM, LEAVE_A_TEAM)) + if(isnull(option)) + balloon_alert(user, "no choice made!") + return + switch(option) + if(JOIN_A_TEAM) + var/picked_team = tgui_input_list(user, "Choose which team to join.", "Team Manager", managed_teams) + if(isnull(picked_team)) + balloon_alert(user, "no choice made!") + return + var/datum/wargaming_team/wargame_team = managed_teams[picked_team] + if(user in wargame_team.team_players) + balloon_alert(user, "already in!") + return + wargame_team.team_players += user + if(LEAVE_A_TEAM) + var/picked_team = tgui_input_list(user, "Choose which team to leave.", "Team Manager", managed_teams) + if(isnull(picked_team)) + balloon_alert(user, "no choice made!") + return + var/datum/wargaming_team/wargame_team = managed_teams[picked_team] + if(!(user in wargame_team.team_players)) + balloon_alert(user, "not in team!") + return + wargame_team.team_players -= user + +#undef JOIN_A_TEAM +#undef LEAVE_A_TEAM + +/// Advances to the next phase, changing to the next team in the list +/obj/item/wargame_base_station/proc/advance_phase(mob/living/user) + if(game_phase == WARGAME_PHASE_NOTHING) + balloon_alert(user, "not running!") + return // ?? + var/certainty = tgui_alert(user, "Advance to the next game phase?", "Advance Phase", list(MY_CHILD_WILL, MY_CHILD_WILL_NOT)) + if(certainty != MY_CHILD_WILL) + return + switch(game_phase) + if(WARGAME_PHASE_PLACEMENT) + game_phase = WARGAME_PHASE_ACTION + say("Action phase, [team_turn.team_name], turn [turn_counter].") + if(WARGAME_PHASE_ACTION) + if(!length(teams_per_turn)) + game_phase = WARGAME_PHASE_EFFECTS + say("Effects phase, turn [turn_counter].") + else + game_phase = WARGAME_PHASE_ACTION + team_turn = teams_per_turn[teams_per_turn[1]] + teams_per_turn -= team_turn + say("Action phase, [team_turn.team_name], turn [turn_counter].") + if(WARGAME_PHASE_EFFECTS) + teams_per_turn = just_team_datums() + turn_counter += 1 + say("Turn [turn_counter - 1] complete, beginning turn [turn_counter].") + team_turn = teams_per_turn[teams_per_turn[1]] + teams_per_turn -= team_turn + game_phase = WARGAME_PHASE_ACTION + say("Action phase, [team_turn.team_name], turn [turn_counter].") + /// Ends the game if it is currently running /obj/item/wargame_base_station/proc/end_the_game(mob/living/user) if(game_phase == WARGAME_PHASE_NOTHING) @@ -63,25 +195,49 @@ if(certainty != MY_CHILD_WILL) return game_phase = WARGAME_PHASE_NOTHING + say("Game ended.") /// Starts the game if it has not already been started /obj/item/wargame_base_station/proc/start_the_game(mob/living/user) if(game_phase != WARGAME_PHASE_NOTHING) balloon_alert(user, "already started!") return - if(length(managed_teams) <= 1) + var/number_of_valid_teams = 0 + for(var/team as anything in managed_teams) + var/datum/wargaming_team/team_datum = managed_teams[team] + if(length(team_datum.team_players)) + number_of_valid_teams++ + if(number_of_valid_teams <= 1) // This counts teams that actually have players in them balloon_alert(user, "too few teams!") return var/certainty = tgui_alert(user, "Are you certain you wish to start the game?", "Game Starter", list(MY_CHILD_WILL, MY_CHILD_WILL_NOT)) if(certainty != MY_CHILD_WILL) return game_phase = WARGAME_PHASE_PLACEMENT + for(var/team as anything in managed_teams) + var/datum/wargaming_team/team_datum = managed_teams[team] + if(!length(team_datum.team_players)) + qdel(team_datum) + managed_teams -= team // Empty teams are wiped before the game starts + shuffle(managed_teams) + teams_per_turn = just_team_datums() + team_turn = teams_per_turn[teams_per_turn[1]] + teams_per_turn -= team_turn + turn_counter = 1 + say("Beginning game, placement phase.") + +/// Returns a list of just team datums from the managed_teams list +/obj/item/wargame_base_station/proc/just_team_datums() + var/team_list = list() + for(var/team as anything in managed_teams) + team_list += managed_teams[team] + return team_list /// Asks for confirmation before deleting every team /obj/item/wargame_base_station/proc/reset_everything(mob/living/user) var/certainty = tgui_alert(user, "Are you certain you wish to delete every team?", "Deletion Confirmation", list(MY_CHILD_WILL, MY_CHILD_WILL_NOT)) if(certainty == MY_CHILD_WILL) - if(var/team as anything in managed_teams) + for(var/team as anything in managed_teams) qdel(managed_teams[team]) managed_teams -= team @@ -103,18 +259,20 @@ var/new_name = tgui_input_text(user, "Give your new team a name.", "Team Name", "Unnamed Combatants") if(isnull(new_name)) balloon_alert(user, "needs name!") - continue + show_team_edit_radial(user, edited_team) + return if(new_name in managed_teams) balloon_alert(user, "name in use!") - continue + show_team_edit_radial(user, edited_team) + return edited_team.team_name = new_name if(BASESTATION_TEAM_COLOR) var/new_color = input(user, "Choose your new team color." ,"Color Selection", COLOR_PRIDE_PURPLE) as color|null if(isnull(new_color)) balloon_alert(user, "needs color!") - continue + show_team_edit_radial(user, edited_team) + return edited_team.team_color = new_color - show_team_edit_radial(user, edited_team) /// Lets players pick from a list of existing teams to delete one /obj/item/wargame_base_station/proc/delete_team(mob/living/user) diff --git a/modular_doppler/wargaming/code/holograms.dm b/modular_doppler/wargaming/code/holograms.dm index e12b121a81cd58..3adc24dfbf6992 100644 --- a/modular_doppler/wargaming/code/holograms.dm +++ b/modular_doppler/wargaming/code/holograms.dm @@ -10,6 +10,10 @@ var/obj/item/wargame_projector/projector /// If this hologram ignores pixel shifting when placed, instead using swarming var/swarming = FALSE + /// The team datum responsible for managing this hologram, if any + var/datum/weakref/team_reference + /// Does this controllable thing require a mob interacting with it to be on its team + var/requires_same_team = TRUE /obj/structure/wargame_hologram/Initialize(mapload, source_projector) . = ..() @@ -18,6 +22,7 @@ LAZYADD(projector.projections, src) if(swarming) AddComponent(/datum/component/swarming) + layer = LOW_ITEM_LAYER /obj/structure/wargame_hologram/Destroy() if(projector) @@ -36,71 +41,62 @@ /// Handles controlling the hologram when clicked on /obj/structure/wargame_hologram/proc/hologram_controls(mob/living/user) - return - -/obj/structure/wargame_hologram/controllable - abstract_type = /obj/structure/wargame_hologram/controllable - /// The team datum responsible for managing this hologram, if any - var/datum/wargaming_team/owner_team - /// Does this controllable thing require a mob interacting with it to be on its team - var/requires_same_team = TRUE - -/obj/structure/wargame_hologram/controllable/hologram_controls(mob/living/user) SHOULD_CALL_PARENT(TRUE) if(requires_same_team && !verify_user_team(user)) balloon_alert(user, "wrong team!") return /// Checks if the user is in the team datum's players list -/obj/structure/wargame_hologram/controllable/proc/verify_user_team(mob/living/user) - if(!owner_team) +/obj/structure/wargame_hologram/proc/verify_user_team(mob/living/user) + var/datum/wargaming_team/owner_team = team_reference?.resolve() + if(isnull(owner_team)) return FALSE if(user in owner_team.team_players) return TRUE return FALSE -/obj/structure/wargame_hologram/controllable/ship - abstract_type = /obj/structure/wargame_hologram/controllable/ship +/obj/structure/wargame_hologram/ship + abstract_type = /obj/structure/wargame_hologram/ship swarming = TRUE /// Projections for 'moving vessels' in order from smallest to largest representation -/obj/structure/wargame_hologram/strike_craft +/obj/structure/wargame_hologram/ship/strike_craft name = "strike craft marker" desc = "A hologram of a single strike craft." icon_state = "strikesingle" -/obj/structure/wargame_hologram/strike_craft_util +/obj/structure/wargame_hologram/ship/strike_craft_util name = "skiff marker" desc = "A hologram of a single utility skiff." icon_state = "strike_utility" -/obj/structure/wargame_hologram/strike_craft/wing +/obj/structure/wargame_hologram/ship/strike_craft/wing name = "strike craft wing marker" desc = "A hologram of a wing of strike craft." icon_state = "strikewing" -/obj/structure/wargame_hologram/ship_marker +/obj/structure/wargame_hologram/ship/ship_marker name = "small vessel marker" desc = "A hologram of a small frigate." icon_state = "smallship" -/obj/structure/wargame_hologram/ship_marker/medium +/obj/structure/wargame_hologram/ship/ship_marker/medium name = "medium vessel marker" desc = "A hologram of a destroyer." icon_state = "mediumship" -/obj/structure/wargame_hologram/ship_marker/large +/obj/structure/wargame_hologram/ship/ship_marker/large name = "large vessel marker" desc = "A hologram of a large cruiser." icon_state = "bigship" -/obj/structure/wargame_hologram/ship_marker/large/alternate +/obj/structure/wargame_hologram/ship/ship_marker/large/alternate name = "alternate large vessel marker" desc = "A hologram of a massive ship." icon_state = "bigship_alternate" -/obj/structure/wargame_hologram/unidentified +/obj/structure/wargame_hologram/ship/unidentified name = "unidentified contact marker" desc = "A hologram standing for an unidentified contact." icon_state = "unidentified" diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index ec4f18ba2b5832..9446c19d819e63 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -39,6 +39,13 @@ /// A names to path list for the projections filled out by populate_radial_choice_lists() on init var/list/projection_names_to_path = list() + /// Weakref to a linked base station, required for placing holograms + var/datum/weakref/linked_base_station + /// If this projector requires a linked team in order to place holograms as well + var/requires_linked_team = FALSE + /// Weakref to a linked team, for drawing color from + var/datum/weakref/linked_team + /obj/item/wargame_projector/Initialize(mapload) . = ..() update_appearance() @@ -53,10 +60,9 @@ /obj/item/wargame_projector/examine(mob/user) . = ..() - if(projections) - . += span_notice("It is currently maintaining [projections.len]/[max_signs] projections.") . += span_notice("Use the projector in hand to change what type of hologram it creates.") - . += span_notice("Alt clicking the projector will let you change the color of the next hologram it makes.") + if(!requires_linked_team) + . += span_notice("Alt clicking the projector will let you change the color of the next hologram it makes.") . += span_warning("Control clicking the projector will allow you to clear all active holograms.") /obj/item/wargame_projector/proc/populate_radial_choice_lists() @@ -82,6 +88,8 @@ select_hologram(user) /obj/item/wargame_projector/click_alt(mob/user) + if(requires_linked_team) + return ..() // Controllers linked to a team are locked to that team's color var/selected_color = tgui_input_list(user, "Select a color", "Color Selection", color_options) if(isnull(selected_color)) balloon_alert(user, "no color change") @@ -99,20 +107,30 @@ return CLICK_ACTION_SUCCESS /// Can we place a hologram at the target location? -/obj/item/wargame_projector/proc/check_can_place_hologram(atom/target, mob/user, team) +/obj/item/wargame_projector/proc/check_can_place_hologram(atom/target, mob/user) + var/obj/item/wargame_base_station/base_station = linked_base_station?.resolve() + if(isnull(base_station)) + user.balloon_alert(user, "no basestation!") + return FALSE + if(base_station.game_phase != WARGAME_PHASE_PLACEMENT) + user.balloon_alert(user, "wrong game phase!") + return FALSE if(!check_allowed_items(target, not_inside = TRUE)) return FALSE var/turf/target_turf = get_turf(target) if(target_turf.is_blocked_turf(TRUE)) return FALSE - if(LAZYLEN(projections) >= max_signs) - balloon_alert(user, "max capacity!") - return FALSE return TRUE /// Spawn a hologram with pixel offset based on where the user clicked /obj/item/wargame_projector/proc/create_hologram(atom/target, mob/user, list/modifiers) - var/obj/target_holosign = new holosign_type(get_turf(target), src) + var/obj/structure/wargame_hologram/target_holosign = new holosign_type(get_turf(target), src) + target_holosign.color = holosign_color + playsound(loc, 'sound/machines/click.ogg', 20, TRUE) + if(requires_linked_team) + target_holosign.team_reference = linked_team + if(target_holosign.swarming) + return // Ships use the swarming component and don't get specific pixel offsets var/click_x var/click_y if(LAZYACCESS(modifiers, ICON_X) && LAZYACCESS(modifiers, ICON_Y)) @@ -120,14 +138,30 @@ click_y = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(world.icon_size/2), world.icon_size/2) target_holosign.pixel_x = click_x target_holosign.pixel_y = click_y - target_holosign.color = holosign_color - playsound(loc, 'sound/machines/click.ogg', 20, TRUE) /obj/item/wargame_projector/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + if(istype(interacting_with, /obj/item/wargame_base_station)) + var/obj/item/wargame_base_station/base_station = interacting_with + balloon_alert(user, "linking...") + var/datum/wargaming_team/reference_team + if(requires_linked_team) + var/picked_team = tgui_input_list(user, "Pick a team to link to.", "Team Picker", base_station.managed_teams) + if(isnull(picked_team)) + base_station.balloon_alert(user, "needs team!") + return NONE + linked_team = WEAKREF(base_station.managed_teams[picked_team]) + reference_team = linked_team.resolve() + if(!do_after(user, 3 SECONDS, base_station)) + return NONE + if(!isnull(reference_team)) + holosign_color = reference_team.team_color + update_appearance() + linked_base_station = WEAKREF(base_station) + return ITEM_INTERACT_SUCCESS if(istype(interacting_with, /obj/structure/wargame_hologram)) qdel(interacting_with) return ITEM_INTERACT_SUCCESS - if(!check_can_place_hologram(interacting_with, user, 1)) + if(!check_can_place_hologram(interacting_with, user)) return NONE create_hologram(interacting_with, user, modifiers) return ITEM_INTERACT_SUCCESS @@ -141,7 +175,6 @@ /obj/item/wargame_projector/ships name = "holographic unit projector" desc = "A handy-dandy holographic projector developed by the Port Authority Naval Command for playing wargames with, this one creates markers for 'units'." - max_signs = 30 holosign_color = COLOR_BLUE_LIGHT holosign_type = /obj/structure/wargame_hologram/ship_marker holosign_options = list( @@ -158,6 +191,7 @@ /obj/structure/wargame_hologram/stationary_structure, /obj/structure/wargame_hologram/stationary_structure/platform, ) + requires_linked_team = TRUE /obj/item/wargame_projector/ships/red holosign_color = COLOR_RED_LIGHT @@ -165,7 +199,6 @@ /obj/item/wargame_projector/terrain name = "holographic terrain projector" desc = "A handy-dandy holographic projector developed by the Port Authority Naval Command for playing wargames with, this one creates markers for space 'terrain'." - max_signs = 30 holosign_color = COLOR_GRAY holosign_type = /obj/structure/wargame_hologram/asteroid // Some things, like stations, probes, and unidentified contacts, can be in the terrain one just because I can see situations where that's desired diff --git a/modular_doppler/wargaming/icons/actions.dmi b/modular_doppler/wargaming/icons/actions.dmi index 29a937b40080459d878f2231dd4bb306085f6c51..093b1ec380c7465bda16fef9119ccc93514311da 100644 GIT binary patch literal 2127 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 FV1n-01D5~* literal 1756 zcmV<21|#{2P)V=-0C=2@&^-#nFboCY**=9y_R_z#OGyYBnkyJny~c(l23fS%uiHSk5^wLJ z4+&J8YuojMX>PrKf>Vf#xtQ~AuhlD%_*_m$tvtf$Wi3gZAjgW4AeB}WIOTrO>cMWt znt#d>S%I53Az^}`%z}cH$sAbhm3|5XYZO5EPeyHzC23vP9w!LJ(FKf+*x5gy6w*LQZ?hG5!g9 z@ZcpUiGP6v!9zfifx!g{!5@JT;zw`Oa~>es*OP0#Ms2OjVB`!O^7 zt?sJoe%;%3j^j9v<2a79aGq-Y14?=Q&6oG&xgWoOVEXr*JN4?^cTDd6oc#Hz&#smH zu+8&Zd)P{zwE*J3bos4LZkkbQxwihyY?GpfznGG_~d&R)O)_Zbai&z8)cv0 zzWTDe|HZdvM#yn7R`Gy~`tj~x$@!O-nt`3$jpfM2^N9um{A{~gd|~NV?JaAt(91Gz-pccH7V%WLD-~b z@@#1lV?Qv87F;cRzX0MRh)U244vhun+z+xkVj+6bBZG3*sKND#0j!ZhbZBg+Kd%hP zO$R>T!~o!^je2Ut2G&5x2q6xwXh-FZnBzE(<2a7vIF92uj^jAaYOvHpq^HilWA>8g z-tBLy|JIz>lc~ec#p?sc2I8N%a=D`o)Rb$DIW$LzUPGqN1>!8N0XUEE{M7Fses*JS zFONglIYIojWy)M2PSPvMi)T+4*PffV?-n1LOXS!$4yw?5k!yWY=s_nc? z1{;Ig$+(i40ft2WXnTEj{NeT8S$Q(;x?6X5gV%em>z|uq7%8p>jKuGwa>!t{#1?BD zOx{g1QZE9DKPTKiA;IZ9_Vg?r50&}(m=RVX2+vx8CO(IYnTz~vX`B$6XcU~eUzcSI zu{B6k3dp<_P&xPI`z#1U5E2D7otFVb<3i$iGCerv`w-_(4`0tg^Zkp2Pe;_2CZ(bV z;d5JXH3wx`TVrK;4#yB5pG$7cf>=u!o6x$j9HMC`)qGydQ_I+>)3<=Q>kGnRsLmPLF}V}v9ZB$0y0d_RD}$OYw~WkNCWLBW1@hqU>2r@)!%4roB zP^&S%LxxE8_bI~`hhi`e;`5rEDlB(ZH9-8KMR_4o)(khV-7zeNBgP2AIr!9c$8j9T zaU5sKSo!>PGMO~}{zK6dKFYWO8si3Nj2oaaZh*$P0UF~5Xp9@6F>ZjynEl7BA^6Pe zWq8NDOpI;-h)+{kY9{y+DX%EUnw&cZqq-n2z6B8fleOLM=Jw<6l`ALv7w+2|A9V7Z z<+S#(8O;Dve2e)(M-kNp)htOOT>LbGF`&kE$eDC5ewg5NM9VKJHNYa4C@tmt01`~j z@lRa~Anz*y;$uo;Y+^2F%yq(J5e>p}j(^TtfZVC*5}J}Yv@TfEdfh-S+5LdjT5XZ@ zTQK&z;aalO&{Sb-T@Zk9Xa(HWgxpT(I$8ieV_6vtjuyeVCIec9AiVPFVQT~UXlP^H y0F7}2G{z0k7&ky;+yISn12o1B&=@yBWBvsfjqe}X#6J`O0000 Date: Fri, 22 May 2026 16:59:19 -0400 Subject: [PATCH 03/36] progress save --- code/__DEFINES/~doppler_defines/wargames.dm | 9 + .../wargaming/code/base_station.dm | 36 ++-- modular_doppler/wargaming/code/holograms.dm | 27 ++- modular_doppler/wargaming/code/projectors.dm | 28 +-- modular_doppler/wargaming/code/team_datums.dm | 13 +- .../wargaming/code/unit_conditions.dm | 24 +++ modular_doppler/wargaming/code/unit_stats.dm | 151 +++++++++++++++ .../wargaming/code/unit_weapons.dm | 180 ++++++++++++++++++ modular_doppler/wargaming/icons/actions.dmi | Bin 2127 -> 4177 bytes tgstation.dme | 3 + 10 files changed, 439 insertions(+), 32 deletions(-) create mode 100644 modular_doppler/wargaming/code/unit_conditions.dm create mode 100644 modular_doppler/wargaming/code/unit_stats.dm create mode 100644 modular_doppler/wargaming/code/unit_weapons.dm diff --git a/code/__DEFINES/~doppler_defines/wargames.dm b/code/__DEFINES/~doppler_defines/wargames.dm index a80a4d93548894..3907493894c769 100644 --- a/code/__DEFINES/~doppler_defines/wargames.dm +++ b/code/__DEFINES/~doppler_defines/wargames.dm @@ -1,3 +1,5 @@ +#define WARGAME_ACTIONS_FILE 'modular_doppler/wargaming/icons/actions.dmi' + /// Current game phase is placing units #define WARGAME_PHASE_PLACEMENT 0 /// Current game phase is action @@ -28,3 +30,10 @@ #define BASESTATION_TEAM_NAME "Team Name" /// Change the color of the currently selected team #define BASESTATION_TEAM_COLOR "Team Color" + +/// Opens a unit's movement options +#define WARGAME_UNIT_MOVE "Move" +/// Opens a unit's attack options +#define WARGAME_UNIT_ATTACK "Attack" +/// Opens a unit's special options +#define WARGAME_UNIT_SPECIAL "Special" diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index 9fc7893dbc2f23..7c66829b9d0818 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -1,5 +1,3 @@ -#define ACTIONS_FILE 'modular_doppler/wargaming/icons/actions.dmi' - /obj/item/wargame_base_station name = "wargames base station" desc = "A base station for holographic wargames, all controllers and interactive wearables link to this machine and are automatically managed by it." @@ -20,24 +18,26 @@ var/datum/wargaming_team/team_turn /// How many turns have passed var/turn_counter = 1 + /// List of any terrain projectors linked to us + var/list/terrain_projectors = list() /// Radial options for before the game has been started var/static/list/pre_game_radial_options = list( - BASESTATION_JOIN_LEAVE = image(icon = ACTIONS_FILE, icon_state = "join_team"), - BASESTATION_ADD_TEAM = image(icon = ACTIONS_FILE, icon_state = "add_team"), - BASESTATION_REMOVE_TEAM = image(icon = ACTIONS_FILE, icon_state = "delete_team"), - BASESTATION_EDIT_TEAM = image(icon = ACTIONS_FILE, icon_state = "edit_team"), - BASESTATION_RESET = image(icon = ACTIONS_FILE, icon_state = "reset"), - BASESTATION_START = image(icon = ACTIONS_FILE, icon_state = "start_game"), + BASESTATION_JOIN_LEAVE = image(icon = WARGAME_ACTIONS_FILE, icon_state = "join_team"), + BASESTATION_ADD_TEAM = image(icon = WARGAME_ACTIONS_FILE, icon_state = "add_team"), + BASESTATION_REMOVE_TEAM = image(icon = WARGAME_ACTIONS_FILE, icon_state = "delete_team"), + BASESTATION_EDIT_TEAM = image(icon = WARGAME_ACTIONS_FILE, icon_state = "edit_team"), + BASESTATION_RESET = image(icon = WARGAME_ACTIONS_FILE, icon_state = "reset"), + BASESTATION_START = image(icon = WARGAME_ACTIONS_FILE, icon_state = "start_game"), ) /// Radial options for after the game has already been started var/static/list/mid_game_radial_options = list( - BASESTATION_END = image(icon = ACTIONS_FILE, icon_state = "end_game"), - BASESTATION_NEXT = image(icon = ACTIONS_FILE, icon_state = "next_phase"), + BASESTATION_END = image(icon = WARGAME_ACTIONS_FILE, icon_state = "end_game"), + BASESTATION_NEXT = image(icon = WARGAME_ACTIONS_FILE, icon_state = "next_phase"), ) /// Radial options for editing a team var/static/list/team_edit_radial_options = list( - BASESTATION_TEAM_NAME = image(icon = ACTIONS_FILE, icon_state = "team_name"), - BASESTATION_TEAM_COLOR = image(icon = ACTIONS_FILE, icon_state = "team_color"), + BASESTATION_TEAM_NAME = image(icon = WARGAME_ACTIONS_FILE, icon_state = "team_name"), + BASESTATION_TEAM_COLOR = image(icon = WARGAME_ACTIONS_FILE, icon_state = "team_color"), ) /obj/item/wargame_base_station/examine(mob/user) @@ -46,6 +46,8 @@ // Game stats var/game_stats_text game_stats_text = "The game is currently in the [game_phase_2_text(game_phase)] phase.
" + if(team_turn) + game_stats_text = "It is the turn of team [team_turn.team_name].
" game_stats_text += game_phase_2_desc(game_phase) game_stats_text += "The teams have completed [EXAMINE_HINT("[turn_counter - 1]")] total turns.
" . += fieldset_block(game_phase_2_text(game_phase, TRUE), game_stats_text, "boxed_message") @@ -174,14 +176,16 @@ say("Effects phase, turn [turn_counter].") else game_phase = WARGAME_PHASE_ACTION - team_turn = teams_per_turn[teams_per_turn[1]] + team_turn = teams_per_turn[1] + team_turn.ready_all_units() teams_per_turn -= team_turn say("Action phase, [team_turn.team_name], turn [turn_counter].") if(WARGAME_PHASE_EFFECTS) teams_per_turn = just_team_datums() turn_counter += 1 say("Turn [turn_counter - 1] complete, beginning turn [turn_counter].") - team_turn = teams_per_turn[teams_per_turn[1]] + team_turn = teams_per_turn[1] + team_turn.ready_all_units() teams_per_turn -= team_turn game_phase = WARGAME_PHASE_ACTION say("Action phase, [team_turn.team_name], turn [turn_counter].") @@ -221,7 +225,8 @@ managed_teams -= team // Empty teams are wiped before the game starts shuffle(managed_teams) teams_per_turn = just_team_datums() - team_turn = teams_per_turn[teams_per_turn[1]] + team_turn = teams_per_turn[1] + team_turn.ready_all_units() teams_per_turn -= team_turn turn_counter = 1 say("Beginning game, placement phase.") @@ -308,4 +313,3 @@ #undef MY_CHILD_WILL #undef MY_CHILD_WILL_NOT -#undef ACTIONS_FILE diff --git a/modular_doppler/wargaming/code/holograms.dm b/modular_doppler/wargaming/code/holograms.dm index 3adc24dfbf6992..e3080583b712fb 100644 --- a/modular_doppler/wargaming/code/holograms.dm +++ b/modular_doppler/wargaming/code/holograms.dm @@ -14,14 +14,20 @@ var/datum/weakref/team_reference /// Does this controllable thing require a mob interacting with it to be on its team var/requires_same_team = TRUE + /// Is this hologram controllable in any way, false for things like dust clouds and asteroids + var/controllable = FALSE + /// This object's unit stats datum, to be created on init + var/datum/wargame_unit_stats/unit_stats = /datum/wargame_unit_stats/generic /obj/structure/wargame_hologram/Initialize(mapload, source_projector) . = ..() + unit_stats = new unit_stats() + chat_color = color if(source_projector) projector = source_projector LAZYADD(projector.projections, src) if(swarming) - AddComponent(/datum/component/swarming) + AddComponent(/datum/component/swarming, max_x = 12, max_y = 12) layer = LOW_ITEM_LAYER /obj/structure/wargame_hologram/Destroy() @@ -35,16 +41,32 @@ . += emissive_appearance(icon, icon_state, src) /obj/structure/wargame_hologram/attack_hand(mob/living/user, list/modifiers) - if(user.combat_mode) + if(user.combat_mode || !controllable) return ..() hologram_controls(user) /// Handles controlling the hologram when clicked on /obj/structure/wargame_hologram/proc/hologram_controls(mob/living/user) SHOULD_CALL_PARENT(TRUE) + if(!currently_our_turn()) + balloon_alert(user, "not our turn!") + return if(requires_same_team && !verify_user_team(user)) balloon_alert(user, "wrong team!") return + unit_stats.basic_actions(user, src) + +/// Checks with our projector's linked controller to see if it's our turn to move +/obj/structure/wargame_hologram/proc/currently_our_turn() + var/obj/item/wargame_base_station/base_station = projector.linked_base_station?.resolve() + if(isnull(base_station)) + return FALSE + var/datum/wargaming_team/our_team = team_reference?.resolve() + if(isnull(our_team)) + return FALSE + if(base_station.team_turn != our_team) + return FALSE + return TRUE /// Checks if the user is in the team datum's players list /obj/structure/wargame_hologram/proc/verify_user_team(mob/living/user) @@ -58,6 +80,7 @@ /obj/structure/wargame_hologram/ship abstract_type = /obj/structure/wargame_hologram/ship swarming = TRUE + controllable = TRUE /// Projections for 'moving vessels' in order from smallest to largest representation diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index 9446c19d819e63..c1ed698fd40e11 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -149,8 +149,9 @@ if(isnull(picked_team)) base_station.balloon_alert(user, "needs team!") return NONE - linked_team = WEAKREF(base_station.managed_teams[picked_team]) - reference_team = linked_team.resolve() + reference_team = base_station.managed_teams[picked_team] + reference_team.tracked_projectors += src + linked_team = WEAKREF(reference_team) if(!do_after(user, 3 SECONDS, base_station)) return NONE if(!isnull(reference_team)) @@ -168,6 +169,9 @@ /obj/item/wargame_projector/Destroy() QDEL_LAZYLIST(projections) + var/datum/wargaming_team/our_team = linked_team?.resolve() + if(!isnull(our_team)) + our_team.tracked_projectors -= src . = ..() /// Actual projector types, split between the 'categories' of things they can project @@ -176,17 +180,17 @@ name = "holographic unit projector" desc = "A handy-dandy holographic projector developed by the Port Authority Naval Command for playing wargames with, this one creates markers for 'units'." holosign_color = COLOR_BLUE_LIGHT - holosign_type = /obj/structure/wargame_hologram/ship_marker + holosign_type = /obj/structure/wargame_hologram/ship/ship_marker holosign_options = list( - /obj/structure/wargame_hologram/unidentified, + /obj/structure/wargame_hologram/ship/unidentified, /obj/structure/wargame_hologram/missile_warning, - /obj/structure/wargame_hologram/strike_craft, - /obj/structure/wargame_hologram/strike_craft_util, - /obj/structure/wargame_hologram/strike_craft/wing, - /obj/structure/wargame_hologram/ship_marker, - /obj/structure/wargame_hologram/ship_marker/medium, - /obj/structure/wargame_hologram/ship_marker/large, - /obj/structure/wargame_hologram/ship_marker/large/alternate, + /obj/structure/wargame_hologram/ship/strike_craft, + /obj/structure/wargame_hologram/ship/strike_craft_util, + /obj/structure/wargame_hologram/ship/strike_craft/wing, + /obj/structure/wargame_hologram/ship/ship_marker, + /obj/structure/wargame_hologram/ship/ship_marker/medium, + /obj/structure/wargame_hologram/ship/ship_marker/large, + /obj/structure/wargame_hologram/ship/ship_marker/large/alternate, /obj/structure/wargame_hologram/probe, /obj/structure/wargame_hologram/stationary_structure, /obj/structure/wargame_hologram/stationary_structure/platform, @@ -203,7 +207,7 @@ holosign_type = /obj/structure/wargame_hologram/asteroid // Some things, like stations, probes, and unidentified contacts, can be in the terrain one just because I can see situations where that's desired holosign_options = list( - /obj/structure/wargame_hologram/unidentified, + /obj/structure/wargame_hologram/ship/unidentified, /obj/structure/wargame_hologram/dust, /obj/structure/wargame_hologram/asteroid, /obj/structure/wargame_hologram/asteroid/large, diff --git a/modular_doppler/wargaming/code/team_datums.dm b/modular_doppler/wargaming/code/team_datums.dm index 926237e113e232..e9be725d0e9641 100644 --- a/modular_doppler/wargaming/code/team_datums.dm +++ b/modular_doppler/wargaming/code/team_datums.dm @@ -6,5 +6,14 @@ var/team_color = "#fff" /// The players currently on this team var/list/team_players = list() - /// Associated list of equipment to the player that linked it - var/list/players_equipment = list() + /// Tracks hologram projectors associated with this team for updating unit action points with + var/list/tracked_projectors = list() + +/// Sets every controllable hologram in our tracked controllers back to its default amount of action points +/datum/wargaming_team/proc/ready_all_units() + for(var/obj/item/wargame_projector/projector as anything in tracked_projectors) + for(var/obj/structure/wargame_hologram/hologram as anything in projector.projections) + if(hologram.controllable) + hologram.unit_stats.make_ready() + +/// Tells every controllable hologram to update it's conditions and explode if necessary diff --git a/modular_doppler/wargaming/code/unit_conditions.dm b/modular_doppler/wargaming/code/unit_conditions.dm new file mode 100644 index 00000000000000..a6a7979f72c1ed --- /dev/null +++ b/modular_doppler/wargaming/code/unit_conditions.dm @@ -0,0 +1,24 @@ +/datum/wargame_condition + abstract_type = /datum/wargame_condition + /// The name of the condition + var/condition_name + /// How many more turns until the condition goes away on its own + var/condition_lifetime_left + +/// When the condition is applied to a unit stats, what changes +/datum/wargame_condition/proc/applied_to_unit(datum/wargame_unit_stats/stats) + return + +/// When the condition is removed from a unit stats, what changes back +/datum/wargame_condition/proc/removed_from_unit(datum/wargame_unit_stats/stats) + return + +/datum/wargame_condition/hull_damage + condition_name = "Hull Damage" + condition_lifetime_left = 2 + +/datum/wargame_condition/hull_damage/applied_to_unit(datum/wargame_unit_stats/stats) + stats.armor_class -= 1 + +/datum/wargame_condition/hull_damage/removed_from_unit(datum/wargame_unit_stats/stats) + stats.armor_class += 1 diff --git a/modular_doppler/wargaming/code/unit_stats.dm b/modular_doppler/wargaming/code/unit_stats.dm new file mode 100644 index 00000000000000..1acd46f214ebea --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats.dm @@ -0,0 +1,151 @@ +/datum/wargame_unit_stats + abstract_type = /datum/wargame_unit_stats + /// The class of unit, "missile destroyer" or "asteroid" as examples + var/unit_class + /// Does this unit generate a random name for itself? Use only on ships + var/generates_name = FALSE + /// This unit's name, automatically generated or set manually. Can be null for no special name + var/unit_name + /// How many conditions this unit can sustain before it is destroyed + var/conditions_limit = 3 + /// A list of conditions currently applied on this unit + var/list/current_conditions = list() + /// List of all possible conditions that can be applied to this unit + var/list/possible_conditions = list() + /// This unit's armor class, or the amount an attack needs to roll above in order to cause a condition + var/armor_class = 10 + /// This unit's evasion, added to the armor class for applicable attacks such as gunfire + var/evasion_modifier = 0 + /// This unit's movement cost, how many action points to move one tile + var/movement_cost = 1 + /// This unit's maximum action points per turn + var/maximum_action_points = 3 + /// This unit's current action points + var/action_points = 0 + /// If this unit counts as a "small vessel" for the purpose of attacks + var/is_small_vessel = FALSE + /// Associative list of weapon to radial choice, should be a list of weapons on init + var/list/weaponry = list() + +/datum/wargame_unit_stats/New() + set_up_weaponry() + +/// Sets up weapon datums and radial options +/datum/wargame_unit_stats/proc/set_up_weaponry() + if(!length(weaponry)) + return // We just don't have weapons + var/list/temporary_weapons = list() + for(var/datum/wargame_weapon/weapon as anything in weaponry) + var/datum/radial_menu_choice/choice = new() + choice.name = weapon.weapon_name + choice.icon = image(icon = WARGAME_ACTIONS_FILE, icon_state = weapon.radial_icon_state) + choice.info = weapon.weapon_description() + temporary_weapons[weapon] = choice + +/// Sets our action points back to max +/datum/wargame_unit_stats/proc/make_ready() + action_points = maximum_action_points + +/// Runs through everything we might need to process during the effects phase +/datum/wargame_unit_stats/proc/effects_phase_process() + for(var/datum/wargame_condition/condition as anything in current_conditions) + condition.condition_lifetime_left-- + if(condition.condition_lifetime_left <= 0) + condition.removed_from_unit() + current_conditions -= condition + qdel(condition) + +/// Shows the menu for basic actions before moving into details +/datum/wargame_unit_stats/proc/basic_actions(mob/living/user, obj/hologram) + var/list/basic_actions_radial = get_unit_basic_actions() + var/radial_choice = show_radial_menu(user, hologram, basic_actions_radial) + if(isnull(radial_choice)) + return + perform_basic_actions(radial_choice, user, hologram) + +/// Returns a list of basic actions this unit can perform +/datum/wargame_unit_stats/proc/get_unit_basic_actions() + var/static/our_actions = list( + WARGAME_UNIT_MOVE = image(icon = WARGAME_ACTIONS_FILE, icon_state = "unit_move"), + WARGAME_UNIT_ATTACK = image(icon = WARGAME_ACTIONS_FILE, icon_state = "unit_attack"), + WARGAME_UNIT_SPECIAL = image(icon = WARGAME_ACTIONS_FILE, icon_state = "unit_special"), + ) + return our_actions + +/// Breaks out into our different procs for actions based on what was chosen +/datum/wargame_unit_stats/proc/perform_basic_actions(radial_choice, mob/living/user, obj/hologram) + if(isnull(radial_choice)) + return + switch(radial_choice) + if(WARGAME_UNIT_MOVE) + unit_movement(user, hologram) + if(WARGAME_UNIT_ATTACK) + unit_attacks(user, hologram) + +/// Brings up a list of weapons to try and attack targets with +/datum/wargame_unit_stats/proc/unit_attacks(mob/living/user, obj/hologram) + var/datum/wargame_weapon/weapon_choice = show_radial_menu(user, hologram, weaponry) + if(isnull(weapon_choice)) + return + var/obj/structure/wargame_hologram/target_hologram = weapon_choice.pick_target(user, hologram) + if(isnull(target_hologram)) + return + if(!weapon_choice.prefire_checks(user, src)) + return + if(!weapon_choice.all_special_effects) + target_hologram.get_attacked(user, hologram, weapon_choice) + weapon_choice.special_effects_fire(user, src) + +/// Calculates getting attacked +/datum/wargame_unit_stats/proc/get_attacked(mob/living/user, obj/hologram, datum/wargame_weapon/weapon_used) + if(isnull(weapon_used)) + return + var/incoming_attack_roll = roll(weapon_used.attack_roll) + if(is_small_vessel && weapon_used.small_ship_disadvantage) + incoming_attack_roll = min(incoming_attack_roll, roll(weapon_used.attack_roll)) + var/total_armor_class = weapon_used.evadable ? (armor_class + evasion_modifier) : armor_class + if(incoming_attack_roll <= incoming_attack_roll) + return + if((incoming_attack_roll + weapon_used.damage_roll_bonus) <= armor_class) + return // If our weapon is weak (PDC) then it makes our attack roll less than armor class + var/datum/wargame_condition/new_condition = pick(possible_conditions) + new_condition = new() + new_condition.applied_to_unit(src) + current_conditions += new_condition + +/// Pulls up an 8 dir radial wheel for movement, also handles checking if we have the action points to move or not +/datum/wargame_unit_stats/proc/unit_movement(mob/living/user, obj/hologram) + if(isnull(hologram)) + return // ?? + if(movement_cost > action_points) + hologram.balloon_alert(user, "not enough AP!") + return + var/picked_dir_string = show_radial_menu(user, hologram, GLOB.all_radial_directions) + if(isnull(picked_dir_string)) + return + var/picked_dir = text2dir(picked_dir_string) + try_move_adjacent(hologram, picked_dir) + action_points -= movement_cost + post_move_effects(hologram) + +/// Any effects to do post movement, like voicelines or sound effects +/datum/wargame_unit_stats/proc/post_move_effects(obj/hologram) + hologram.say("Moving to location.") + +/datum/wargame_unit_stats/generic + unit_class = "Generic Thing" + generates_name = FALSE + unit_name = "Unidentified Thing" + conditions_limit = 2 + possible_conditions = list( + /datum/wargame_condition/hull_damage, + ) + armor_class = 5 + evasion_modifier = 0 + movement_cost = 1 + maximum_action_points = 3 + weaponry = list( + /datum/wargame_weapon/medium_cannon, + /datum/wargame_weapon/pdc, + /datum/wargame_weapon/minefield, + ) diff --git a/modular_doppler/wargaming/code/unit_weapons.dm b/modular_doppler/wargaming/code/unit_weapons.dm new file mode 100644 index 00000000000000..f0ed7f2f4a6ae3 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons.dm @@ -0,0 +1,180 @@ +/datum/wargame_weapon + abstract_type = /datum/wargame_weapon + /// The name of the weapon + var/weapon_name + /// The attack roll of the weapon if within range + var/attack_roll + /// A negative number to make weapons that are weak to armor less effective against high armor class + var/damage_roll_bonus + /// The weapon range in tiles, 0 is the same tile as the ship firing it + var/attack_range + /// If this attack can be evaded, giving ships with an evasion bonus extra armor class + var/evadable + /// If this weapon has roll disadvantage against small ships + var/small_ship_disadvantage + /// The icon_state to use for this weapon's radial menu option + var/radial_icon_state + /// If this weapon has a limited number of uses before needing to be re-armed + var/maximum_ammo + /// If this weapon's attack is handled entirely by special_effects_fire() + var/all_specal_effects = FALSE + +/// Returns a string to describe the weapon +/datum/wargame_weapon/proc/weapon_description() + return + +/// By default, checks in a circle range around the ship for another hologram to target +/datum/wargame_weapon/proc/pick_target(mob/living/user, obj/structure/wargame_hologram/hologram) + var/datum/wargaming_team/hologram_team = hologram.team_reference?.resolve() + var/list/potential_targets = list() + for(var/obj/structure/wargame_hologram/other_hologram in range(attack_range)) + if(isnull(hologram_team)) + potential_targets += other_hologram + continue // If we have no team then free for all everything's a target + var/datum/wargaming_team/other_hologram_team = other_hologram.team_reference?.resolve() + if(other_hologram_team == hologram_team) + continue // Cannot target friendlies otherwise + potential_targets += other_hologram + if(!length(potential_targets)) + hologram.balloon_alert(user, "no targets!") + return + var/picked_target = tgui_input_list(user, "Pick a target within range.", "Target Picker", potential_targets) + if(isnull(picked_target)) + hologram.balloon_alert(user, "no choice!") + return + return picked_target + +/// If this weapon has any special pre-fire checks +/datum/wargame_weapon/proc/prefire_checks(mob/living/user, obj/structure/wargame_hologram/hologram) + if(isnull(maximum_ammo)) + return TRUE + if(maximum_ammo <= 0) + hologram.balloon_alert(user, "no ammo!") + return FALSE + return TRUE + +/// If this weapon does anything special when initially firing +/datum/wargame_weapon/proc/special_effects_fire(mob/living/user, obj/structure/wargame_hologram/hologram) + if(isnull(maximum_ammo)) + return + maximum_ammo-- + +/datum/wargame_weapon/mass_driver + weapon_name = "Mass Driver" + attack_roll = "2d10+4" + damage_roll_bonus = 0 + attack_range = 3 + evadable = TRUE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_guns_big" + +/datum/wargame_weapon/mass_driver/weapon_description() + return "A large mass driver for launching hardened cores at large targets. Max range of [attack_range] tiles." + +/datum/wargame_weapon/large_cannon + weapon_name = "7\" Cannon" + attack_roll = "2d16+4" + damage_roll_bonus = 0 + attack_range = 2 + evadable = TRUE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_guns_big" + +/datum/wargame_weapon/large_cannon/weapon_description() + return "A large calibre 7 in. cannon for launching shells at large targets. Max range of [attack_range] tiles." + +/datum/wargame_weapon/medium_cannon + weapon_name = "2.5\" Cannon" + attack_roll = "4d5+4" + damage_roll_bonus = 0 + attack_range = 2 + evadable = TRUE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_guns_big" + +/datum/wargame_weapon/medium_cannon/weapon_description() + return "A medium calibre 2.5 in. cannon fore launching shells at large targets. Max range of [attack_range] tiles." + +/datum/wargame_weapon/rockets + weapon_name = "Rockets" + attack_roll = "6d4" + damage_roll_bonus = 0 + attack_range = 2 + evadable = TRUE + radial_icon_state = "weapon_rocket" + +/datum/wargame_weapon/rockets/strike + maximum_ammo = 2 + +/datum/wargame_weapon/rockets/weapon_description() + return "A barrage of unguided rockets from a fixed rack or large launcher. Max range of [attack_range] tiles." + +/datum/wargame_weapon/bombs + weapon_name = "Bombs" + attack_roll = "1d20" + damage_roll_bonus = 0 + attack_range = 1 + evadable = TRUE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_bomb" + maximum_ammo = 2 + +/datum/wargame_weapon/bombs/weapon_description() + return "A group of unpowered explosive bombs to crack large targets. Max range of [attack_range] tiles." + +/datum/wargame_weapon/minefield + weapon_name = "Mines" + attack_roll = "3d8" + damage_roll_bonus = 0 + attack_range = 0 + evadable = TRUE + radial_icon_state = "weapon_mine" + maximum_ammo = 6 + +/datum/wargame_weapon/minefield/weapon_description() + return "A dense field of mines that only allow friendly signals to pass. Will only damage ships that pass through them." + +/datum/wargame_weapon/autocannon + weapon_name = "45mm Autocannon" + attack_roll = "2d10+2" + damage_roll_bonus = 0 + attack_range = 1 + evadable = TRUE + radial_icon_state = "weapon_guns" + +/datum/wargame_weapon/autocannon/weapon_description() + return "Low calibre 45mm autocannons for fast moving targets. Max range of [attack_range] tiles." + +/datum/wargame_weapon/pdc + weapon_name = "26mm PDC" + attack_roll = "1d12+8" + damage_roll_bonus = -2 + attack_range = 1 + evadable = FALSE + radial_icon_state = "weapon_guns_pdc" + +/datum/wargame_weapon/pdc/weapon_description() + return "Small calibre 26mm autocannons with exceptional hit chance but low damage. Max range of [attack_range] tiles." + +/datum/wargame_weapon/anti_ship_beam + weapon_name = "Anti-Ship Beam" + attack_roll = "1d20+5" + damage_roll_bonus = 0 + attack_range = 1 + evadable = FALSE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_beam" + +/datum/wargame_weapon/anti_ship_beam/weapon_description() + return "A high-energy beam for coring large ships with. Max range of [attack_range] tiles." + +/datum/wargame_weapon/pd_beam + weapon_name = "PD Beam" + attack_roll = "1d20+6" + damage_roll_bonus = -4 + attack_range = 2 + evadable = FALSE + radial_icon_state = "weapon_pd_beam" + +/datum/wargame_weapon/pd_beam/weapon_description() + return "Highly focused beams with exceptional hit chance but low damage. Max range of [attack_range] tiles." diff --git a/modular_doppler/wargaming/icons/actions.dmi b/modular_doppler/wargaming/icons/actions.dmi index 093b1ec380c7465bda16fef9119ccc93514311da..2b85178ae000a598f9c7b2d1f4336f0ff3af683f 100644 GIT binary patch literal 4177 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 FV1n-01D5~* diff --git a/tgstation.dme b/tgstation.dme index 658436c0356d00..68eaf04cf058e2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -7959,6 +7959,9 @@ #include "modular_doppler\wargaming\code\holograms.dm" #include "modular_doppler\wargaming\code\projectors.dm" #include "modular_doppler\wargaming\code\team_datums.dm" +#include "modular_doppler\wargaming\code\unit_conditions.dm" +#include "modular_doppler\wargaming\code\unit_stats.dm" +#include "modular_doppler\wargaming\code\unit_weapons.dm" #include "modular_doppler\z_lore_rename\pa_4ca.dm" #include "modular_doppler\z_lore_rename\syndicate_removal.dm" // END_INCLUDE From 074b31705eccd838f4f44c8a453d8aab36e29c70 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Fri, 22 May 2026 17:17:47 -0400 Subject: [PATCH 04/36] attacks real? --- .../wargaming/code/base_station.dm | 10 +++++ modular_doppler/wargaming/code/team_datums.dm | 5 +++ modular_doppler/wargaming/code/unit_stats.dm | 18 +++++--- .../wargaming/code/unit_weapons.dm | 41 +++++++++++++------ 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index 7c66829b9d0818..38a0b04c9b96d1 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -118,6 +118,13 @@ In order to start, the game needs at least two teams with at least one person each. These do not need to be different people, allowing \ for solo play.
" +/// Updates every hologram run by a terrain projector +/obj/item/wargame_base_station/proc/update_terrain_holograms() + for(var/obj/item/wargame_projector/projector as anything in terrain_projectors) + for(var/obj/structure/wargame_hologram/hologram as anything in projector.projections) + if(hologram.controllable) + hologram.unit_stats.effects_phase_process(hologram) + #define MY_CHILD_WILL "Yes" #define MY_CHILD_WILL_NOT "No" @@ -172,6 +179,9 @@ say("Action phase, [team_turn.team_name], turn [turn_counter].") if(WARGAME_PHASE_ACTION) if(!length(teams_per_turn)) + update_terrain_holograms() + for(var/datum/wargaming_team/team as anything in just_team_datums()) + team.update_all_units() game_phase = WARGAME_PHASE_EFFECTS say("Effects phase, turn [turn_counter].") else diff --git a/modular_doppler/wargaming/code/team_datums.dm b/modular_doppler/wargaming/code/team_datums.dm index e9be725d0e9641..c68b7da69075db 100644 --- a/modular_doppler/wargaming/code/team_datums.dm +++ b/modular_doppler/wargaming/code/team_datums.dm @@ -17,3 +17,8 @@ hologram.unit_stats.make_ready() /// Tells every controllable hologram to update it's conditions and explode if necessary +/datum/wargaming_team/proc/update_all_units() + for(var/obj/item/wargame_projector/projector as anything in tracked_projectors) + for(var/obj/structure/wargame_hologram/hologram as anything in projector.projections) + if(hologram.controllable) + hologram.unit_stats.effects_phase_process(hologram) diff --git a/modular_doppler/wargaming/code/unit_stats.dm b/modular_doppler/wargaming/code/unit_stats.dm index 1acd46f214ebea..8c063cfc9cd9d9 100644 --- a/modular_doppler/wargaming/code/unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats.dm @@ -24,6 +24,8 @@ var/action_points = 0 /// If this unit counts as a "small vessel" for the purpose of attacks var/is_small_vessel = FALSE + /// If this unit can be targetted by others at all, use for background stuff like space dust + var/can_be_a_target = TRUE /// Associative list of weapon to radial choice, should be a list of weapons on init var/list/weaponry = list() @@ -38,7 +40,7 @@ for(var/datum/wargame_weapon/weapon as anything in weaponry) var/datum/radial_menu_choice/choice = new() choice.name = weapon.weapon_name - choice.icon = image(icon = WARGAME_ACTIONS_FILE, icon_state = weapon.radial_icon_state) + choice.image = image(icon = WARGAME_ACTIONS_FILE, icon_state = weapon.radial_icon_state) choice.info = weapon.weapon_description() temporary_weapons[weapon] = choice @@ -47,13 +49,19 @@ action_points = maximum_action_points /// Runs through everything we might need to process during the effects phase -/datum/wargame_unit_stats/proc/effects_phase_process() +/datum/wargame_unit_stats/proc/effects_phase_process(obj/structure/wargame_hologram/hologram) for(var/datum/wargame_condition/condition as anything in current_conditions) condition.condition_lifetime_left-- if(condition.condition_lifetime_left <= 0) condition.removed_from_unit() current_conditions -= condition qdel(condition) + if(length(current_conditions) > conditions_limit) + im_boutta_blow(hologram) + +/// What to do when this unit explodes, good place to spawn a replacement "wreck" unit type +/datum/wargame_unit_stats/proc/im_boutta_blow(obj/structure/wargame_hologram/hologram) + qdel(hologram) /// Shows the menu for basic actions before moving into details /datum/wargame_unit_stats/proc/basic_actions(mob/living/user, obj/hologram) @@ -93,8 +101,8 @@ if(!weapon_choice.prefire_checks(user, src)) return if(!weapon_choice.all_special_effects) - target_hologram.get_attacked(user, hologram, weapon_choice) - weapon_choice.special_effects_fire(user, src) + target_hologram.unit_stats.get_attacked(user, hologram, weapon_choice) + weapon_choice.special_effects_fire(user, src, hologram) /// Calculates getting attacked /datum/wargame_unit_stats/proc/get_attacked(mob/living/user, obj/hologram, datum/wargame_weapon/weapon_used) @@ -104,7 +112,7 @@ if(is_small_vessel && weapon_used.small_ship_disadvantage) incoming_attack_roll = min(incoming_attack_roll, roll(weapon_used.attack_roll)) var/total_armor_class = weapon_used.evadable ? (armor_class + evasion_modifier) : armor_class - if(incoming_attack_roll <= incoming_attack_roll) + if(incoming_attack_roll <= total_armor_class) return if((incoming_attack_roll + weapon_used.damage_roll_bonus) <= armor_class) return // If our weapon is weak (PDC) then it makes our attack roll less than armor class diff --git a/modular_doppler/wargaming/code/unit_weapons.dm b/modular_doppler/wargaming/code/unit_weapons.dm index f0ed7f2f4a6ae3..c656ac70a0022f 100644 --- a/modular_doppler/wargaming/code/unit_weapons.dm +++ b/modular_doppler/wargaming/code/unit_weapons.dm @@ -16,8 +16,10 @@ var/radial_icon_state /// If this weapon has a limited number of uses before needing to be re-armed var/maximum_ammo + /// How many action points this weapon costs to fire + var/action_point_cost /// If this weapon's attack is handled entirely by special_effects_fire() - var/all_specal_effects = FALSE + var/all_special_effects = FALSE /// Returns a string to describe the weapon /datum/wargame_weapon/proc/weapon_description() @@ -28,6 +30,8 @@ var/datum/wargaming_team/hologram_team = hologram.team_reference?.resolve() var/list/potential_targets = list() for(var/obj/structure/wargame_hologram/other_hologram in range(attack_range)) + if(!other_hologram.unit_stats.can_be_a_target) + continue if(isnull(hologram_team)) potential_targets += other_hologram continue // If we have no team then free for all everything's a target @@ -45,7 +49,10 @@ return picked_target /// If this weapon has any special pre-fire checks -/datum/wargame_weapon/proc/prefire_checks(mob/living/user, obj/structure/wargame_hologram/hologram) +/datum/wargame_weapon/proc/prefire_checks(mob/living/user, datum/wargame_unit_stats/stats, obj/structure/wargame_hologram/hologram) + if(stats.action_points < action_point_cost) + hologram.balloon_alert(user, "not enough AP!") + return FALSE if(isnull(maximum_ammo)) return TRUE if(maximum_ammo <= 0) @@ -67,9 +74,10 @@ evadable = TRUE small_ship_disadvantage = TRUE radial_icon_state = "weapon_guns_big" + action_point_cost = 1 /datum/wargame_weapon/mass_driver/weapon_description() - return "A large mass driver for launching hardened cores at large targets. Max range of [attack_range] tiles." + return "A large mass driver for launching hardened cores at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/large_cannon weapon_name = "7\" Cannon" @@ -79,9 +87,10 @@ evadable = TRUE small_ship_disadvantage = TRUE radial_icon_state = "weapon_guns_big" + action_point_cost = 1 /datum/wargame_weapon/large_cannon/weapon_description() - return "A large calibre 7 in. cannon for launching shells at large targets. Max range of [attack_range] tiles." + return "A large calibre 7 in. cannon for launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/medium_cannon weapon_name = "2.5\" Cannon" @@ -91,9 +100,10 @@ evadable = TRUE small_ship_disadvantage = TRUE radial_icon_state = "weapon_guns_big" + action_point_cost = 1 /datum/wargame_weapon/medium_cannon/weapon_description() - return "A medium calibre 2.5 in. cannon fore launching shells at large targets. Max range of [attack_range] tiles." + return "A medium calibre 2.5 in. cannon fore launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/rockets weapon_name = "Rockets" @@ -102,12 +112,13 @@ attack_range = 2 evadable = TRUE radial_icon_state = "weapon_rocket" + action_point_cost = 1 /datum/wargame_weapon/rockets/strike maximum_ammo = 2 /datum/wargame_weapon/rockets/weapon_description() - return "A barrage of unguided rockets from a fixed rack or large launcher. Max range of [attack_range] tiles." + return "A barrage of unguided rockets from a fixed rack or large launcher. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/bombs weapon_name = "Bombs" @@ -118,9 +129,10 @@ small_ship_disadvantage = TRUE radial_icon_state = "weapon_bomb" maximum_ammo = 2 + action_point_cost = 1 /datum/wargame_weapon/bombs/weapon_description() - return "A group of unpowered explosive bombs to crack large targets. Max range of [attack_range] tiles." + return "A group of unpowered explosive bombs to crack large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/minefield weapon_name = "Mines" @@ -130,9 +142,10 @@ evadable = TRUE radial_icon_state = "weapon_mine" maximum_ammo = 6 + action_point_cost = 2 /datum/wargame_weapon/minefield/weapon_description() - return "A dense field of mines that only allow friendly signals to pass. Will only damage ships that pass through them." + return "A dense field of mines that only allow friendly signals to pass. Will only damage ships that pass through them. [action_point_cost] AP to fire." /datum/wargame_weapon/autocannon weapon_name = "45mm Autocannon" @@ -141,9 +154,10 @@ attack_range = 1 evadable = TRUE radial_icon_state = "weapon_guns" + action_point_cost = 1 /datum/wargame_weapon/autocannon/weapon_description() - return "Low calibre 45mm autocannons for fast moving targets. Max range of [attack_range] tiles." + return "Low calibre 45mm autocannons for fast moving targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/pdc weapon_name = "26mm PDC" @@ -152,9 +166,10 @@ attack_range = 1 evadable = FALSE radial_icon_state = "weapon_guns_pdc" + action_point_cost = 1 /datum/wargame_weapon/pdc/weapon_description() - return "Small calibre 26mm autocannons with exceptional hit chance but low damage. Max range of [attack_range] tiles." + return "Small calibre 26mm autocannons with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/anti_ship_beam weapon_name = "Anti-Ship Beam" @@ -164,9 +179,10 @@ evadable = FALSE small_ship_disadvantage = TRUE radial_icon_state = "weapon_beam" + action_point_cost = 2 /datum/wargame_weapon/anti_ship_beam/weapon_description() - return "A high-energy beam for coring large ships with. Max range of [attack_range] tiles." + return "A high-energy beam for coring large ships with. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/pd_beam weapon_name = "PD Beam" @@ -175,6 +191,7 @@ attack_range = 2 evadable = FALSE radial_icon_state = "weapon_pd_beam" + action_point_cost = 1 /datum/wargame_weapon/pd_beam/weapon_description() - return "Highly focused beams with exceptional hit chance but low damage. Max range of [attack_range] tiles." + return "Highly focused beams with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." From bc56cd5f158869a15b94f833fe6286b7c038da5b Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Fri, 22 May 2026 23:31:41 -0400 Subject: [PATCH 05/36] iron --- code/__DEFINES/~doppler_defines/wargames.dm | 14 ++ .../wargaming/code/base_station.dm | 3 +- .../code/conditions/_unit_conditions.dm | 63 ++++++ modular_doppler/wargaming/code/game_kit.dm | 18 +- modular_doppler/wargaming/code/holograms.dm | 178 ---------------- .../wargaming/code/holograms/_holograms.dm | 104 +++++++++ .../wargaming/code/holograms/asteroid.dm | 17 ++ .../wargaming/code/holograms/corvette.dm | 4 + .../wargaming/code/holograms/cruiser.dm | 14 ++ .../wargaming/code/holograms/destroyer.dm | 14 ++ .../wargaming/code/holograms/missile.dm | 11 + .../wargaming/code/holograms/static.dm | 17 ++ .../wargaming/code/holograms/strike.dm | 9 + modular_doppler/wargaming/code/projectors.dm | 39 ++-- .../wargaming/code/unit_conditions.dm | 24 --- .../_unit_stats.dm} | 133 ++++++++++-- .../wargaming/code/unit_stats/asteroid.dm | 38 ++++ .../wargaming/code/unit_stats/corvette.dm | 26 +++ .../wargaming/code/unit_stats/cruiser.dm | 59 ++++++ .../wargaming/code/unit_stats/destroyer.dm | 54 +++++ .../wargaming/code/unit_stats/missiles.dm | 58 ++++++ .../wargaming/code/unit_stats/static.dm | 60 ++++++ .../wargaming/code/unit_stats/strike.dm | 43 ++++ .../wargaming/code/unit_weapons.dm | 197 ------------------ .../code/unit_weapons/_unit_weapons.dm | 109 ++++++++++ .../wargaming/code/unit_weapons/beams.dm | 46 ++++ .../wargaming/code/unit_weapons/big_guns.dm | 73 +++++++ .../wargaming/code/unit_weapons/bombs.dm | 44 ++++ .../wargaming/code/unit_weapons/missiles.dm | 111 ++++++++++ .../wargaming/code/unit_weapons/ramming.dm | 15 ++ .../wargaming/code/unit_weapons/small_guns.dm | 45 ++++ modular_doppler/wargaming/icons/actions.dmi | Bin 4177 -> 4351 bytes modular_doppler/wargaming/icons/holograms.dmi | Bin 0 -> 2420 bytes modular_doppler/wargaming/icons/projector.dmi | Bin 0 -> 462 bytes .../icons/projectors_and_holograms.dmi | Bin 3779 -> 0 bytes .../wargaming/sound/attributions.txt | 8 + .../wargaming/sound/autocannon.ogg | Bin 0 -> 28654 bytes modular_doppler/wargaming/sound/beam.ogg | Bin 0 -> 14151 bytes modular_doppler/wargaming/sound/cannon.ogg | Bin 0 -> 17473 bytes .../wargaming/sound/missile_launch.ogg | Bin 0 -> 27049 bytes .../wargaming/sound/ship_explode.ogg | Bin 0 -> 33909 bytes modular_doppler/wargaming/sound/ship_hit.ogg | Bin 0 -> 6355 bytes .../wargaming/sound/swarm_launch.ogg | Bin 0 -> 43142 bytes tgstation.dme | 28 ++- 44 files changed, 1219 insertions(+), 457 deletions(-) create mode 100644 modular_doppler/wargaming/code/conditions/_unit_conditions.dm delete mode 100644 modular_doppler/wargaming/code/holograms.dm create mode 100644 modular_doppler/wargaming/code/holograms/_holograms.dm create mode 100644 modular_doppler/wargaming/code/holograms/asteroid.dm create mode 100644 modular_doppler/wargaming/code/holograms/corvette.dm create mode 100644 modular_doppler/wargaming/code/holograms/cruiser.dm create mode 100644 modular_doppler/wargaming/code/holograms/destroyer.dm create mode 100644 modular_doppler/wargaming/code/holograms/missile.dm create mode 100644 modular_doppler/wargaming/code/holograms/static.dm create mode 100644 modular_doppler/wargaming/code/holograms/strike.dm delete mode 100644 modular_doppler/wargaming/code/unit_conditions.dm rename modular_doppler/wargaming/code/{unit_stats.dm => unit_stats/_unit_stats.dm} (53%) create mode 100644 modular_doppler/wargaming/code/unit_stats/asteroid.dm create mode 100644 modular_doppler/wargaming/code/unit_stats/corvette.dm create mode 100644 modular_doppler/wargaming/code/unit_stats/cruiser.dm create mode 100644 modular_doppler/wargaming/code/unit_stats/destroyer.dm create mode 100644 modular_doppler/wargaming/code/unit_stats/missiles.dm create mode 100644 modular_doppler/wargaming/code/unit_stats/static.dm create mode 100644 modular_doppler/wargaming/code/unit_stats/strike.dm delete mode 100644 modular_doppler/wargaming/code/unit_weapons.dm create mode 100644 modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm create mode 100644 modular_doppler/wargaming/code/unit_weapons/beams.dm create mode 100644 modular_doppler/wargaming/code/unit_weapons/big_guns.dm create mode 100644 modular_doppler/wargaming/code/unit_weapons/bombs.dm create mode 100644 modular_doppler/wargaming/code/unit_weapons/missiles.dm create mode 100644 modular_doppler/wargaming/code/unit_weapons/ramming.dm create mode 100644 modular_doppler/wargaming/code/unit_weapons/small_guns.dm create mode 100644 modular_doppler/wargaming/icons/holograms.dmi create mode 100644 modular_doppler/wargaming/icons/projector.dmi delete mode 100644 modular_doppler/wargaming/icons/projectors_and_holograms.dmi create mode 100644 modular_doppler/wargaming/sound/attributions.txt create mode 100644 modular_doppler/wargaming/sound/autocannon.ogg create mode 100644 modular_doppler/wargaming/sound/beam.ogg create mode 100644 modular_doppler/wargaming/sound/cannon.ogg create mode 100644 modular_doppler/wargaming/sound/missile_launch.ogg create mode 100644 modular_doppler/wargaming/sound/ship_explode.ogg create mode 100644 modular_doppler/wargaming/sound/ship_hit.ogg create mode 100644 modular_doppler/wargaming/sound/swarm_launch.ogg diff --git a/code/__DEFINES/~doppler_defines/wargames.dm b/code/__DEFINES/~doppler_defines/wargames.dm index 3907493894c769..ba403ebfaab4c0 100644 --- a/code/__DEFINES/~doppler_defines/wargames.dm +++ b/code/__DEFINES/~doppler_defines/wargames.dm @@ -9,6 +9,20 @@ /// Current game phase is nothing, the game is over or hasn't even started yet #define WARGAME_PHASE_NOTHING 10 +/// Something like a dust cloud that gives an evasion bonus but no cover +#define WARGAME_EVASION_BONUS 0 +/// A small thing with zero cover value +#define WARGAME_SIZE_SMALL 1 +/// A medium sized thing that small things can hide around +#define WARGAME_SIZE_MEDIUM 2 +/// A large thing that even decent sized ships can hide around +#define WARGAME_SIZE_LARGE 3 + +/// The maximum amount of cover bonus a unit can have +#define WARGAME_MAX_COVER_BONUS 4 +/// The maximum amount of evasion bonus a unit can have +#define WARGAME_MAX_EVASION_BONUS 2 + /// Add a team to the base station #define BASESTATION_ADD_TEAM "Add Team" /// Remove a team from the base station diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index 38a0b04c9b96d1..b418fdf0ba7aa3 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -176,14 +176,15 @@ switch(game_phase) if(WARGAME_PHASE_PLACEMENT) game_phase = WARGAME_PHASE_ACTION + team_turn.ready_all_units() say("Action phase, [team_turn.team_name], turn [turn_counter].") if(WARGAME_PHASE_ACTION) if(!length(teams_per_turn)) update_terrain_holograms() + say("Effects phase, turn [turn_counter].") for(var/datum/wargaming_team/team as anything in just_team_datums()) team.update_all_units() game_phase = WARGAME_PHASE_EFFECTS - say("Effects phase, turn [turn_counter].") else game_phase = WARGAME_PHASE_ACTION team_turn = teams_per_turn[1] diff --git a/modular_doppler/wargaming/code/conditions/_unit_conditions.dm b/modular_doppler/wargaming/code/conditions/_unit_conditions.dm new file mode 100644 index 00000000000000..5bd015e9743b5c --- /dev/null +++ b/modular_doppler/wargaming/code/conditions/_unit_conditions.dm @@ -0,0 +1,63 @@ +/datum/wargame_condition + abstract_type = /datum/wargame_condition + /// The name of the condition + var/condition_name + /// Brief description of what the condition does + var/condition_desc + /// How many more turns until the condition goes away on its own + var/condition_lifetime_left + +/// When the condition is applied to a unit stats, what changes +/datum/wargame_condition/proc/applied_to_unit(datum/wargame_unit_stats/stats) + return + +/// When the condition is removed from a unit stats, what changes back +/datum/wargame_condition/proc/removed_from_unit(datum/wargame_unit_stats/stats) + return + +// special condition for missiles just so they explode +/datum/wargame_condition/missile_failure + condition_name = "Missile Failure" + condition_desc = "Complete failure of missile systems, safety self destruct imminent." + condition_lifetime_left = 2 + +/datum/wargame_condition/missile_failure/applied_to_unit(datum/wargame_unit_stats/stats) + stats.maximum_action_points = 0 // Missile is disabled + +/datum/wargame_condition/hull_damage + condition_name = "Hull Damage" + condition_desc = "Damage to the hull that reduces the armor class of the ship." + condition_lifetime_left = 2 + +/datum/wargame_condition/hull_damage/applied_to_unit(datum/wargame_unit_stats/stats) + stats.armor_class -= 1 + +/datum/wargame_condition/hull_damage/removed_from_unit(datum/wargame_unit_stats/stats) + stats.armor_class += 1 + +/datum/wargame_condition/hull_damage/asteroid + condition_name = "Surface Fracture" + condition_desc = "Fracturing of the asteroid surface that reduces the asteroid's armor class." + condition_lifetime_left = 10 + +/datum/wargame_condition/engine_damage + condition_name = "Engine Damage" + condition_desc = "Damage to the engines that makes movement cost an extra action point." + condition_lifetime_left = 2 + +/datum/wargame_condition/engine_damage/applied_to_unit(datum/wargame_unit_stats/stats) + stats.movement_cost += 1 + +/datum/wargame_condition/engine_damage/removed_from_unit(datum/wargame_unit_stats/stats) + stats.movement_cost -= 1 + +/datum/wargame_condition/reactor_damage + condition_name = "Reactor Damage" + condition_desc = "Damage to the ship's reactor that reduces maximum action points by one." + condition_lifetime_left = 3 + +/datum/wargame_condition/engine_damage/applied_to_unit(datum/wargame_unit_stats/stats) + stats.maximum_action_points -= 1 + +/datum/wargame_condition/engine_damage/removed_from_unit(datum/wargame_unit_stats/stats) + stats.maximum_action_points += 1 diff --git a/modular_doppler/wargaming/code/game_kit.dm b/modular_doppler/wargaming/code/game_kit.dm index 729ee80fe39851..f0dd7eff24bb9c 100644 --- a/modular_doppler/wargaming/code/game_kit.dm +++ b/modular_doppler/wargaming/code/game_kit.dm @@ -5,25 +5,9 @@ /obj/item/storage/briefcase/secure/wargame_kit/PopulateContents() var/static/items_inside = list( + /obj/item/wargame_base_station = 1, /obj/item/wargame_projector/ships = 1, /obj/item/wargame_projector/ships/red = 1, /obj/item/wargame_projector/terrain = 1, - /obj/item/storage/dice = 1, - /obj/item/book/manual/wargame_rules = 1, - /obj/item/book/manual/wargame_rules/examples = 1, ) generate_items_inside(items_inside,src) - -/obj/item/book/manual/wargame_rules - name = "Wargame: Blue Lizard - Example Ruleset" - icon_state = "book" - starting_author = "John War - CEO of War" - starting_title = "Wargame: Blue Lizard - Example Ruleset" - starting_content = "Formatting is a fuck - 2564 kill em all - Just go to this link in your browser https://hackmd.io/@Paxilmaniac/H1ZVsTIYR" - -/obj/item/book/manual/wargame_rules/examples - name = "Wargame: Blue Lizard - Example Ships and Scenarios" - icon_state = "book1" - starting_author = "John War - CEO of War" - starting_title = "Wargame: Blue Lizard - Example Ships and Scenarios" - starting_content = "Formatting is a fuck - 2564 kill em all - Just go to this link in your browser https://hackmd.io/@Paxilmaniac/rJwy1C8KR" diff --git a/modular_doppler/wargaming/code/holograms.dm b/modular_doppler/wargaming/code/holograms.dm deleted file mode 100644 index e3080583b712fb..00000000000000 --- a/modular_doppler/wargaming/code/holograms.dm +++ /dev/null @@ -1,178 +0,0 @@ -/obj/structure/wargame_hologram - abstract_type = /obj/structure/wargame_hologram - icon = 'modular_doppler/wargaming/icons/projectors_and_holograms.dmi' - icon_state = null - anchored = TRUE - density = FALSE - max_integrity = 30 - obj_flags = UNIQUE_RENAME - /// What object created this projection? Can be null as a projector isn't required for this to exist - var/obj/item/wargame_projector/projector - /// If this hologram ignores pixel shifting when placed, instead using swarming - var/swarming = FALSE - /// The team datum responsible for managing this hologram, if any - var/datum/weakref/team_reference - /// Does this controllable thing require a mob interacting with it to be on its team - var/requires_same_team = TRUE - /// Is this hologram controllable in any way, false for things like dust clouds and asteroids - var/controllable = FALSE - /// This object's unit stats datum, to be created on init - var/datum/wargame_unit_stats/unit_stats = /datum/wargame_unit_stats/generic - -/obj/structure/wargame_hologram/Initialize(mapload, source_projector) - . = ..() - unit_stats = new unit_stats() - chat_color = color - if(source_projector) - projector = source_projector - LAZYADD(projector.projections, src) - if(swarming) - AddComponent(/datum/component/swarming, max_x = 12, max_y = 12) - layer = LOW_ITEM_LAYER - -/obj/structure/wargame_hologram/Destroy() - if(projector) - LAZYREMOVE(projector.projections, src) - projector = null - return ..() - -/obj/structure/wargame_hologram/update_overlays() - . = ..() - . += emissive_appearance(icon, icon_state, src) - -/obj/structure/wargame_hologram/attack_hand(mob/living/user, list/modifiers) - if(user.combat_mode || !controllable) - return ..() - hologram_controls(user) - -/// Handles controlling the hologram when clicked on -/obj/structure/wargame_hologram/proc/hologram_controls(mob/living/user) - SHOULD_CALL_PARENT(TRUE) - if(!currently_our_turn()) - balloon_alert(user, "not our turn!") - return - if(requires_same_team && !verify_user_team(user)) - balloon_alert(user, "wrong team!") - return - unit_stats.basic_actions(user, src) - -/// Checks with our projector's linked controller to see if it's our turn to move -/obj/structure/wargame_hologram/proc/currently_our_turn() - var/obj/item/wargame_base_station/base_station = projector.linked_base_station?.resolve() - if(isnull(base_station)) - return FALSE - var/datum/wargaming_team/our_team = team_reference?.resolve() - if(isnull(our_team)) - return FALSE - if(base_station.team_turn != our_team) - return FALSE - return TRUE - -/// Checks if the user is in the team datum's players list -/obj/structure/wargame_hologram/proc/verify_user_team(mob/living/user) - var/datum/wargaming_team/owner_team = team_reference?.resolve() - if(isnull(owner_team)) - return FALSE - if(user in owner_team.team_players) - return TRUE - return FALSE - -/obj/structure/wargame_hologram/ship - abstract_type = /obj/structure/wargame_hologram/ship - swarming = TRUE - controllable = TRUE - -/// Projections for 'moving vessels' in order from smallest to largest representation - -/obj/structure/wargame_hologram/ship/strike_craft - name = "strike craft marker" - desc = "A hologram of a single strike craft." - icon_state = "strikesingle" - -/obj/structure/wargame_hologram/ship/strike_craft_util - name = "skiff marker" - desc = "A hologram of a single utility skiff." - icon_state = "strike_utility" - -/obj/structure/wargame_hologram/ship/strike_craft/wing - name = "strike craft wing marker" - desc = "A hologram of a wing of strike craft." - icon_state = "strikewing" - -/obj/structure/wargame_hologram/ship/ship_marker - name = "small vessel marker" - desc = "A hologram of a small frigate." - icon_state = "smallship" - -/obj/structure/wargame_hologram/ship/ship_marker/medium - name = "medium vessel marker" - desc = "A hologram of a destroyer." - icon_state = "mediumship" - -/obj/structure/wargame_hologram/ship/ship_marker/large - name = "large vessel marker" - desc = "A hologram of a large cruiser." - icon_state = "bigship" - -/obj/structure/wargame_hologram/ship/ship_marker/large/alternate - name = "alternate large vessel marker" - desc = "A hologram of a massive ship." - icon_state = "bigship_alternate" - -/obj/structure/wargame_hologram/ship/unidentified - name = "unidentified contact marker" - desc = "A hologram standing for an unidentified contact." - icon_state = "unidentified" - -/* -Projections for misc stuff, like stations, scout probes, or incoming missiles -*/ - -/obj/structure/wargame_hologram/missile_warning - name = "in-flight missile marker" - desc = "A hologram of a missile currently in flight." - icon_state = "missile" - -/obj/structure/wargame_hologram/probe - name = "probe marker" - desc = "A hologram of a scout probe." - icon_state = "probe" - -/obj/structure/wargame_hologram/stationary_structure - name = "station marker" - desc = "A hologram of a space station." - icon_state = "station" - -/obj/structure/wargame_hologram/stationary_structure/platform - name = "platform marker" - desc = "A hologram of a small space platform." - icon_state = "platform" - -/* -Projections for space 'terrain' like asteroids and dust clouds -*/ - -/obj/structure/wargame_hologram/dust - name = "dust field marker" - desc = "A hologram of a field of stellar dust of some sort." - icon_state = "dustcloud" - -/obj/structure/wargame_hologram/asteroid - name = "small asteroid marker" - desc = "A hologram of a small asteroid." - icon_state = "asteroidsmall" - -/obj/structure/wargame_hologram/asteroid/large - name = "large asteroid marker" - desc = "A hologram of a large asteroid." - icon_state = "asteroidlarge" - -/obj/structure/wargame_hologram/asteroid/cluster - name = "asteroid cluster marker" - desc = "A hologram of a cluster of asteroids." - icon_state = "asteroidcluster" - -/obj/structure/wargame_hologram/planet - name = "planetary body marker" - desc = "A hologram of a planet." - icon_state = "planet" diff --git a/modular_doppler/wargaming/code/holograms/_holograms.dm b/modular_doppler/wargaming/code/holograms/_holograms.dm new file mode 100644 index 00000000000000..e0e83621885042 --- /dev/null +++ b/modular_doppler/wargaming/code/holograms/_holograms.dm @@ -0,0 +1,104 @@ +/obj/structure/wargame_hologram + abstract_type = /obj/structure/wargame_hologram + icon = 'modular_doppler/wargaming/icons/holograms.dmi' + icon_state = null + anchored = TRUE + density = FALSE + max_integrity = 30 + alpha = 180 + obj_flags = UNIQUE_RENAME + /// What object created this projection? Can be null as a projector isn't required for this to exist + var/obj/item/wargame_projector/projector + /// If this hologram ignores pixel shifting when placed, instead using swarming + var/swarming = FALSE + /// The team datum responsible for managing this hologram, if any + var/datum/weakref/team_reference + /// Does this controllable thing require a mob interacting with it to be on its team + var/requires_same_team = TRUE + /// Is this hologram controllable in any way, false for things like dust clouds and asteroids + var/controllable = FALSE + /// This object's unit stats datum, to be created on init + var/datum/wargame_unit_stats/unit_stats = /datum/wargame_unit_stats/generic + +/obj/structure/wargame_hologram/Initialize(mapload, source_projector) + . = ..() + unit_stats = new unit_stats() + unit_stats.set_hologram_name(src) + if(source_projector) + projector = source_projector + LAZYADD(projector.projections, src) + if(swarming) + AddComponent(/datum/component/swarming, max_x = 12, max_y = 12) + layer = LOW_ITEM_LAYER + +/obj/structure/wargame_hologram/examine(mob/user) + . = ..() + var/datum/wargaming_team/our_team = team_reference?.resolve() + if(!isnull(our_team)) + . += span_notice("It belongs to the [our_team.team_name] team.") + if(controllable) + . += "This unit has [unit_stats.action_points] / [unit_stats.maximum_action_points] action points." + . += "It costs this unit [unit_stats.movement_cost] action points to move." + if(length(unit_stats.current_conditions)) + . += span_notice("It currently suffers from the following conditions") + var/conditions_text = "" + for(var/datum/wargame_condition/condition as anything in unit_stats.current_conditions) + conditions_text += "- [condition.condition_name] - [condition.condition_desc] - [condition.condition_lifetime_left] turns until cleared.
" + . += boxed_message(conditions_text) + if(length(unit_stats.current_conditions) > unit_stats.conditions_limit) + . += span_warning("It looks like it's nearing its limit, if these conditions don't improve by next effects phase, the ship will be lost.") + +/obj/structure/wargame_hologram/Destroy() + if(projector) + LAZYREMOVE(projector.projections, src) + projector = null + return ..() + +/obj/structure/wargame_hologram/update_overlays() + . = ..() + . += emissive_appearance(icon, icon_state, src) + +/obj/structure/wargame_hologram/attack_hand(mob/living/user, list/modifiers) + if(user.combat_mode || !controllable) + return ..() + hologram_controls(user) + +/// Handles controlling the hologram when clicked on +/obj/structure/wargame_hologram/proc/hologram_controls(mob/living/user) + SHOULD_CALL_PARENT(TRUE) + if(!currently_our_turn()) + if(prob(1/1000)) + say("WAIT. YOUR. TURN!") + else + balloon_alert(user, "not our turn!") + return + if(requires_same_team && !verify_user_team(user)) + balloon_alert(user, "wrong team!") + return + unit_stats.basic_actions(user, src) + +/// Checks with our projector's linked controller to see if it's our turn to move +/obj/structure/wargame_hologram/proc/currently_our_turn() + var/obj/item/wargame_base_station/base_station = projector.linked_base_station?.resolve() + if(isnull(base_station)) + return FALSE + var/datum/wargaming_team/our_team = team_reference?.resolve() + if(isnull(our_team)) + return FALSE + if(base_station.team_turn != our_team) + return FALSE + return TRUE + +/// Checks if the user is in the team datum's players list +/obj/structure/wargame_hologram/proc/verify_user_team(mob/living/user) + var/datum/wargaming_team/owner_team = team_reference?.resolve() + if(isnull(owner_team)) + return FALSE + if(user in owner_team.team_players) + return TRUE + return FALSE + +/obj/structure/wargame_hologram/controllable + abstract_type = /obj/structure/wargame_hologram/controllable + swarming = TRUE + controllable = TRUE diff --git a/modular_doppler/wargaming/code/holograms/asteroid.dm b/modular_doppler/wargaming/code/holograms/asteroid.dm new file mode 100644 index 00000000000000..3a9f599fb40c83 --- /dev/null +++ b/modular_doppler/wargaming/code/holograms/asteroid.dm @@ -0,0 +1,17 @@ +/obj/structure/wargame_hologram/asteroid + name = "asteroid" + icon_state = "asteroidsmall" + unit_stats = /datum/wargame_unit_stats/terrain/asteroid + +/obj/structure/wargame_hologram/asteroid/large + name = "large asteroid" + icon_state = "asteroidlarge" + +/obj/structure/wargame_hologram/asteroid/shattered + name = "asteroid cluster" + icon_state = "asteroidcluster" + +/obj/structure/wargame_hologram/dust + name = /datum/wargame_unit_stats/terrain/dust_cloud::unit_class + icon_state = "dustcloud" + unit_stats = /datum/wargame_unit_stats/terrain/dust_cloud diff --git a/modular_doppler/wargaming/code/holograms/corvette.dm b/modular_doppler/wargaming/code/holograms/corvette.dm new file mode 100644 index 00000000000000..525f7955da505b --- /dev/null +++ b/modular_doppler/wargaming/code/holograms/corvette.dm @@ -0,0 +1,4 @@ +/obj/structure/wargame_hologram/controllable/picket_corvette + name = /datum/wargame_unit_stats/corvette/picket::unit_class + icon_state = "small_ship" + unit_stats = /datum/wargame_unit_stats/corvette/picket diff --git a/modular_doppler/wargaming/code/holograms/cruiser.dm b/modular_doppler/wargaming/code/holograms/cruiser.dm new file mode 100644 index 00000000000000..fbfad9de3ae58c --- /dev/null +++ b/modular_doppler/wargaming/code/holograms/cruiser.dm @@ -0,0 +1,14 @@ +/obj/structure/wargame_hologram/controllable/mass_driver_cruiser + name = /datum/wargame_unit_stats/cruiser/mass_driver::unit_class + icon_state = "large_ship" + unit_stats = /datum/wargame_unit_stats/cruiser/mass_driver + +/obj/structure/wargame_hologram/controllable/artillery_cruiser + name = /datum/wargame_unit_stats/cruiser/artillery::unit_class + icon_state = "large_ship" + unit_stats = /datum/wargame_unit_stats/cruiser/artillery + +/obj/structure/wargame_hologram/controllable/battlecruiser + name = /datum/wargame_unit_stats/cruiser/linebreaker::unit_class + icon_state = "large_ship_alt" + unit_stats = /datum/wargame_unit_stats/cruiser/linebreaker diff --git a/modular_doppler/wargaming/code/holograms/destroyer.dm b/modular_doppler/wargaming/code/holograms/destroyer.dm new file mode 100644 index 00000000000000..6f41e6b013113d --- /dev/null +++ b/modular_doppler/wargaming/code/holograms/destroyer.dm @@ -0,0 +1,14 @@ +/obj/structure/wargame_hologram/controllable/destroyer + name = /datum/wargame_unit_stats/destroyer/brawler::unit_class + icon_state = "med_ship" + unit_stats = /datum/wargame_unit_stats/destroyer/brawler + +/obj/structure/wargame_hologram/controllable/beam_frigate + name = /datum/wargame_unit_stats/destroyer/beam::unit_class + icon_state = "med_ship" + unit_stats = /datum/wargame_unit_stats/destroyer/beam + +/obj/structure/wargame_hologram/controllable/missile_frigate + name = /datum/wargame_unit_stats/destroyer/missile::unit_class + icon_state = "med_ship" + unit_stats = /datum/wargame_unit_stats/destroyer/missile diff --git a/modular_doppler/wargaming/code/holograms/missile.dm b/modular_doppler/wargaming/code/holograms/missile.dm new file mode 100644 index 00000000000000..7aa02ac54f4f36 --- /dev/null +++ b/modular_doppler/wargaming/code/holograms/missile.dm @@ -0,0 +1,11 @@ +/obj/structure/wargame_hologram/controllable/cruise_missile + icon_state = "missile" + unit_stats = /datum/wargame_unit_stats/missile/cruise + +/obj/structure/wargame_hologram/controllable/swarm_missile + icon_state = "missile" + unit_stats = /datum/wargame_unit_stats/missile/swarm + +/obj/structure/wargame_hologram/controllable/torpedo + icon_state = "missile" + unit_stats = /datum/wargame_unit_stats/missile/torpedo diff --git a/modular_doppler/wargaming/code/holograms/static.dm b/modular_doppler/wargaming/code/holograms/static.dm new file mode 100644 index 00000000000000..0dd0dd682bf5bd --- /dev/null +++ b/modular_doppler/wargaming/code/holograms/static.dm @@ -0,0 +1,17 @@ +/obj/structure/wargame_hologram/controllable/platform + swarming = FALSE + name = /datum/wargame_unit_stats/platform/outpost::unit_class + icon_state = "platform" + unit_stats = /datum/wargame_unit_stats/platform/outpost + +/obj/structure/wargame_hologram/controllable/civilian_station + swarming = FALSE + name = /datum/wargame_unit_stats/platform/civilian::unit_class + icon_state = "station" + unit_stats = /datum/wargame_unit_stats/platform/civilian + +/obj/structure/wargame_hologram/controllable/military_station + swarming = FALSE + name = /datum/wargame_unit_stats/platform/citadel::unit_class + icon_state = "station" + unit_stats = /datum/wargame_unit_stats/platform/citadel diff --git a/modular_doppler/wargaming/code/holograms/strike.dm b/modular_doppler/wargaming/code/holograms/strike.dm new file mode 100644 index 00000000000000..caa92b6a05ce1c --- /dev/null +++ b/modular_doppler/wargaming/code/holograms/strike.dm @@ -0,0 +1,9 @@ +/obj/structure/wargame_hologram/controllable/strike_wing + name = /datum/wargame_unit_stats/strike/wing::unit_class + icon_state = "strike_wing" + unit_stats = /datum/wargame_unit_stats/strike/wing + +/obj/structure/wargame_hologram/controllable/interceptor_wing + name = /datum/wargame_unit_stats/strike/interceptor::unit_class + icon_state = "strike" + unit_stats = /datum/wargame_unit_stats/strike/interceptor diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index c1ed698fd40e11..22b4f60757e272 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -1,7 +1,7 @@ /obj/item/wargame_projector name = "holographic wargame projector" desc = "A holographic projectors for creating holograms that work in the wargaming system." - icon = 'modular_doppler/wargaming/icons/projectors_and_holograms.dmi' + icon = 'modular_doppler/wargaming/icons/projector.dmi' icon_state = "projector" base_icon_state = "projector" lefthand_file = 'modular_doppler/wargaming/icons/mob/lefthand.dmi' @@ -178,22 +178,22 @@ /obj/item/wargame_projector/ships name = "holographic unit projector" - desc = "A handy-dandy holographic projector developed by the Port Authority Naval Command for playing wargames with, this one creates markers for 'units'." + desc = "A holographic projectors for creating holograms that work in the wargaming system. Must be linked to a base station and team before use." holosign_color = COLOR_BLUE_LIGHT - holosign_type = /obj/structure/wargame_hologram/ship/ship_marker + holosign_type = /obj/structure/wargame_hologram/controllable/strike_wing holosign_options = list( - /obj/structure/wargame_hologram/ship/unidentified, - /obj/structure/wargame_hologram/missile_warning, - /obj/structure/wargame_hologram/ship/strike_craft, - /obj/structure/wargame_hologram/ship/strike_craft_util, - /obj/structure/wargame_hologram/ship/strike_craft/wing, - /obj/structure/wargame_hologram/ship/ship_marker, - /obj/structure/wargame_hologram/ship/ship_marker/medium, - /obj/structure/wargame_hologram/ship/ship_marker/large, - /obj/structure/wargame_hologram/ship/ship_marker/large/alternate, - /obj/structure/wargame_hologram/probe, - /obj/structure/wargame_hologram/stationary_structure, - /obj/structure/wargame_hologram/stationary_structure/platform, + /obj/structure/wargame_hologram/controllable/strike_wing, + /obj/structure/wargame_hologram/controllable/interceptor_wing, + /obj/structure/wargame_hologram/controllable/picket_corvette, + /obj/structure/wargame_hologram/controllable/missile_frigate, + /obj/structure/wargame_hologram/controllable/beam_frigate, + /obj/structure/wargame_hologram/controllable/destroyer, + /obj/structure/wargame_hologram/controllable/mass_driver_cruiser, + /obj/structure/wargame_hologram/controllable/artillery_cruiser, + /obj/structure/wargame_hologram/controllable/battlecruiser, + /obj/structure/wargame_hologram/controllable/platform, + /obj/structure/wargame_hologram/controllable/civilian_station, + /obj/structure/wargame_hologram/controllable/military_station, ) requires_linked_team = TRUE @@ -207,13 +207,8 @@ holosign_type = /obj/structure/wargame_hologram/asteroid // Some things, like stations, probes, and unidentified contacts, can be in the terrain one just because I can see situations where that's desired holosign_options = list( - /obj/structure/wargame_hologram/ship/unidentified, - /obj/structure/wargame_hologram/dust, /obj/structure/wargame_hologram/asteroid, /obj/structure/wargame_hologram/asteroid/large, - /obj/structure/wargame_hologram/asteroid/cluster, - /obj/structure/wargame_hologram/planet, - /obj/structure/wargame_hologram/probe, - /obj/structure/wargame_hologram/stationary_structure, - /obj/structure/wargame_hologram/stationary_structure/platform, + /obj/structure/wargame_hologram/asteroid/shattered, + /obj/structure/wargame_hologram/dust, ) diff --git a/modular_doppler/wargaming/code/unit_conditions.dm b/modular_doppler/wargaming/code/unit_conditions.dm deleted file mode 100644 index a6a7979f72c1ed..00000000000000 --- a/modular_doppler/wargaming/code/unit_conditions.dm +++ /dev/null @@ -1,24 +0,0 @@ -/datum/wargame_condition - abstract_type = /datum/wargame_condition - /// The name of the condition - var/condition_name - /// How many more turns until the condition goes away on its own - var/condition_lifetime_left - -/// When the condition is applied to a unit stats, what changes -/datum/wargame_condition/proc/applied_to_unit(datum/wargame_unit_stats/stats) - return - -/// When the condition is removed from a unit stats, what changes back -/datum/wargame_condition/proc/removed_from_unit(datum/wargame_unit_stats/stats) - return - -/datum/wargame_condition/hull_damage - condition_name = "Hull Damage" - condition_lifetime_left = 2 - -/datum/wargame_condition/hull_damage/applied_to_unit(datum/wargame_unit_stats/stats) - stats.armor_class -= 1 - -/datum/wargame_condition/hull_damage/removed_from_unit(datum/wargame_unit_stats/stats) - stats.armor_class += 1 diff --git a/modular_doppler/wargaming/code/unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm similarity index 53% rename from modular_doppler/wargaming/code/unit_stats.dm rename to modular_doppler/wargaming/code/unit_stats/_unit_stats.dm index 8c063cfc9cd9d9..3509b55e4469e0 100644 --- a/modular_doppler/wargaming/code/unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -6,6 +6,8 @@ var/generates_name = FALSE /// This unit's name, automatically generated or set manually. Can be null for no special name var/unit_name + /// This unit's description + var/unit_description /// How many conditions this unit can sustain before it is destroyed var/conditions_limit = 3 /// A list of conditions currently applied on this unit @@ -26,23 +28,42 @@ var/is_small_vessel = FALSE /// If this unit can be targetted by others at all, use for background stuff like space dust var/can_be_a_target = TRUE + /// How big this unit is compared to others, used for cover calculations + var/unit_size = WARGAME_SIZE_SMALL + /// If this unit is talkative and will speak voicelines during specific actions + var/talkative = TRUE /// Associative list of weapon to radial choice, should be a list of weapons on init var/list/weaponry = list() /datum/wargame_unit_stats/New() set_up_weaponry() +/// Sets the name of the attached hologram +/datum/wargame_unit_stats/proc/set_hologram_name(obj/structure/wargame_hologram/hologram) + if(!isnull(unit_description)) + hologram.desc = unit_description + if(isnull(unit_class) && isnull(unit_name)) + return + if(generates_name) + unit_name = pick_list_replacements("~doppler/salvage_shuttle.json", "ship_name") + if(isnull(unit_name)) + hologram.name = unit_class + else + hologram.name = "[unit_class] - ([unit_name])" + /// Sets up weapon datums and radial options /datum/wargame_unit_stats/proc/set_up_weaponry() if(!length(weaponry)) return // We just don't have weapons var/list/temporary_weapons = list() for(var/datum/wargame_weapon/weapon as anything in weaponry) + weapon = new weapon() var/datum/radial_menu_choice/choice = new() choice.name = weapon.weapon_name choice.image = image(icon = WARGAME_ACTIONS_FILE, icon_state = weapon.radial_icon_state) choice.info = weapon.weapon_description() temporary_weapons[weapon] = choice + weaponry = temporary_weapons /// Sets our action points back to max /datum/wargame_unit_stats/proc/make_ready() @@ -61,6 +82,19 @@ /// What to do when this unit explodes, good place to spawn a replacement "wreck" unit type /datum/wargame_unit_stats/proc/im_boutta_blow(obj/structure/wargame_hologram/hologram) + if(talkative) + var/static/list/lines = list( + "Reactor critical, all crew punch out!", + "We're losing her!", + "It's not- It's not shutting down!", + "Damage critical, all crew to lifeboats.", + "Red alert! Abandon ship!", + "Send for rescue! We're-", + "Punch out! Punch out! We're had!", + ) + hologram.say(pick(lines)) + playsound(hologram, 'modular_doppler/wargaming/sound/ship_explode.ogg', 50, TRUE) + do_sparks(3, FALSE, hologram) qdel(hologram) /// Shows the menu for basic actions before moving into details @@ -76,7 +110,7 @@ var/static/our_actions = list( WARGAME_UNIT_MOVE = image(icon = WARGAME_ACTIONS_FILE, icon_state = "unit_move"), WARGAME_UNIT_ATTACK = image(icon = WARGAME_ACTIONS_FILE, icon_state = "unit_attack"), - WARGAME_UNIT_SPECIAL = image(icon = WARGAME_ACTIONS_FILE, icon_state = "unit_special"), + // WARGAME_UNIT_SPECIAL = image(icon = WARGAME_ACTIONS_FILE, icon_state = "unit_special"), // To come later ) return our_actions @@ -98,28 +132,90 @@ var/obj/structure/wargame_hologram/target_hologram = weapon_choice.pick_target(user, hologram) if(isnull(target_hologram)) return - if(!weapon_choice.prefire_checks(user, src)) + if(!weapon_choice.prefire_checks(user, src, hologram)) return + if(weapon_choice.firing_voiceline() && talkative) + hologram.say(weapon_choice.firing_voiceline()) if(!weapon_choice.all_special_effects) - target_hologram.unit_stats.get_attacked(user, hologram, weapon_choice) - weapon_choice.special_effects_fire(user, src, hologram) + var/impact = target_hologram.unit_stats.get_attacked(user, hologram, target_hologram, weapon_choice) + weapon_choice.special_effects_fire(user, src, hologram, target_hologram, impact) + else + weapon_choice.special_effects_fire(user, src, hologram, target_hologram) -/// Calculates getting attacked -/datum/wargame_unit_stats/proc/get_attacked(mob/living/user, obj/hologram, datum/wargame_weapon/weapon_used) +/// Calculates getting attacked, returns TRUE if the weapon actually made impact with the enemy, even if it did nothing +/datum/wargame_unit_stats/proc/get_attacked(mob/living/user, obj/structure/wargame_hologram/attacking_hologram, obj/structure/wargame_hologram/hologram, datum/wargame_weapon/weapon_used) if(isnull(weapon_used)) - return + return FALSE var/incoming_attack_roll = roll(weapon_used.attack_roll) if(is_small_vessel && weapon_used.small_ship_disadvantage) incoming_attack_roll = min(incoming_attack_roll, roll(weapon_used.attack_roll)) - var/total_armor_class = weapon_used.evadable ? (armor_class + evasion_modifier) : armor_class + var/total_armor_class = weapon_used.evadable ? (calculate_armor_class(hologram) + calculate_evasion_mod(hologram)) : calculate_armor_class(hologram) if(incoming_attack_roll <= total_armor_class) - return + if(attacking_hologram.unit_stats.talkative) + attacking_hologram.say(missed_voiceline()) + hologram.visible_message(span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was less than or equal to the target's effective armor class, [total_armor_class].")) + return FALSE if((incoming_attack_roll + weapon_used.damage_roll_bonus) <= armor_class) - return // If our weapon is weak (PDC) then it makes our attack roll less than armor class + if(attacking_hologram.unit_stats.talkative) + attacking_hologram.say(nonpen_voiceline()) + hologram.visible_message(span_warning("Attacker's damage roll, [incoming_attack_roll + weapon_used.damage_roll_bonus], was less than or equal to the target's armor class, [armor_class].")) + playsound(hologram, 'modular_doppler/wargaming/sound/ship_hit.ogg', 50, TRUE) + return TRUE // If our weapon is weak (PDC) then it makes our attack roll less than armor class var/datum/wargame_condition/new_condition = pick(possible_conditions) - new_condition = new() + new_condition = new new_condition() new_condition.applied_to_unit(src) current_conditions += new_condition + if(attacking_hologram.unit_stats.talkative) + attacking_hologram.say(good_hit_voiceline()) + hologram.visible_message(span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was higher than the target's effective armor class, [total_armor_class].")) + hologram.Shake(2, 0, 2 SECONDS) + playsound(hologram, 'modular_doppler/wargaming/sound/ship_hit.ogg', 50, TRUE) + return TRUE + +/// Returns a voiceline for missing the target +/datum/wargame_unit_stats/proc/missed_voiceline() + var/static/list/lines = list( + "Bad target track, we missed the target!", + "We have missed the target.", + "Looks like we missed!", + ) + return pick(lines) + +/// Returns a voiceline for hitting the target and doing nothing +/datum/wargame_unit_stats/proc/nonpen_voiceline() + var/static/list/lines = list( + "Hit- We didn't even scratch them!", + "No effect on the target, bad hit!", + "All we did was scratch the paint!", + ) + return pick(lines) + +/// Returns a voiceline for hitting the target and damaging it +/datum/wargame_unit_stats/proc/good_hit_voiceline() + var/static/list/lines = list( + "Confirming good hits.", + "We hit them! Good effect on target!", + "Yes, a hit!", + ) + return pick(lines) + +/// Calculates the ship's armor class based on cover on the same tile +/datum/wargame_unit_stats/proc/calculate_armor_class(obj/hologram) + var/cover_modifier = 0 + var/turf/cover_turf = get_turf(hologram) + for(var/obj/structure/wargame_hologram/cover_hologram in cover_turf.contents) + if(cover_hologram.unit_stats.unit_size > unit_size) + cover_modifier++ + return armor_class + min(cover_modifier, WARGAME_MAX_COVER_BONUS) + +/// Calculates the ship's evasion bonus based on evasion boosters in the tile +/datum/wargame_unit_stats/proc/calculate_evasion_mod(obj/hologram) + var/evasion_modifier = 0 + var/turf/evasion_turf = get_turf(hologram) + for(var/obj/structure/wargame_hologram/evasion_hologram in evasion_turf.contents) + if(evasion_hologram.unit_stats.unit_size == WARGAME_EVASION_BONUS) + evasion_modifier++ + return evasion_modifier + min(evasion_modifier, WARGAME_MAX_EVASION_BONUS) /// Pulls up an 8 dir radial wheel for movement, also handles checking if we have the action points to move or not /datum/wargame_unit_stats/proc/unit_movement(mob/living/user, obj/hologram) @@ -138,11 +234,20 @@ /// Any effects to do post movement, like voicelines or sound effects /datum/wargame_unit_stats/proc/post_move_effects(obj/hologram) - hologram.say("Moving to location.") + if(prob(50)) + return + var/static/list/lines = list( + "Moving to designated coordinates.", + "Moving to positions.", + "Displacing.", + "Getting there.", + "Starting burn.", + ) + hologram.say("[pick(lines)]") /datum/wargame_unit_stats/generic unit_class = "Generic Thing" - generates_name = FALSE + generates_name = TRUE unit_name = "Unidentified Thing" conditions_limit = 2 possible_conditions = list( @@ -152,8 +257,8 @@ evasion_modifier = 0 movement_cost = 1 maximum_action_points = 3 + unit_size = WARGAME_SIZE_MEDIUM weaponry = list( /datum/wargame_weapon/medium_cannon, /datum/wargame_weapon/pdc, - /datum/wargame_weapon/minefield, ) diff --git a/modular_doppler/wargaming/code/unit_stats/asteroid.dm b/modular_doppler/wargaming/code/unit_stats/asteroid.dm new file mode 100644 index 00000000000000..31334531b8a92f --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/asteroid.dm @@ -0,0 +1,38 @@ +/datum/wargame_unit_stats/terrain + abstract_type = /datum/wargame_unit_stats/terrain + conditions_limit = 4 + possible_conditions = list( + /datum/wargame_condition/hull_damage, + /datum/wargame_condition/engine_damage, + /datum/wargame_condition/reactor_damage, + ) + is_small_vessel = FALSE + unit_size = WARGAME_SIZE_LARGE + talkative = FALSE + +/datum/wargame_unit_stats/terrain/get_unit_basic_actions() + return + +/datum/wargame_unit_stats/terrain/im_boutta_blow(obj/structure/wargame_hologram/hologram) + hologram.visible_message(span_warning("[hologram] cracks into pieces before fading into a digital cloud!")) + return ..() + +/datum/wargame_unit_stats/terrain/asteroid + unit_class = "asteroid" + unit_description = "A large asteroid that serves as good cover. Can be broken apart with sufficient damage." + armor_class = 13 + evasion_modifier = 0 + movement_cost = 100 + maximum_action_points = 0 + weaponry = list() + +/datum/wargame_unit_stats/terrain/dust_cloud + unit_class = "dust cloud" + unit_description = "A large cloud of space dust of some kind. While unserviceable as cover, gives a boost to evasion while inside of it." + armor_class = 200 + evasion_modifier = 0 + movement_cost = 100 + maximum_action_points = 0 + can_be_a_target = FALSE + unit_size = WARGAME_EVASION_BONUS + weaponry = list() diff --git a/modular_doppler/wargaming/code/unit_stats/corvette.dm b/modular_doppler/wargaming/code/unit_stats/corvette.dm new file mode 100644 index 00000000000000..8d56917e8e0350 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/corvette.dm @@ -0,0 +1,26 @@ +/datum/wargame_unit_stats/corvette + abstract_type = /datum/wargame_unit_stats/corvette + generates_name = TRUE + conditions_limit = 2 + possible_conditions = list( + /datum/wargame_condition/hull_damage, + /datum/wargame_condition/engine_damage, + /datum/wargame_condition/reactor_damage, + ) + is_small_vessel = TRUE + unit_size = WARGAME_SIZE_SMALL + +/datum/wargame_unit_stats/corvette/im_boutta_blow(obj/structure/wargame_hologram/hologram) + hologram.visible_message(span_warning("[hologram] flashes and disappears as its reactor goes critical!")) + return ..() + +/datum/wargame_unit_stats/corvette/picket + unit_class = "picket corvette" + unit_description = "A corvette fitted with PDC for fleet defence." + armor_class = 8 + evasion_modifier = 2 + movement_cost = 1 + maximum_action_points = 3 + weaponry = list( + /datum/wargame_weapon/pdc, + ) diff --git a/modular_doppler/wargaming/code/unit_stats/cruiser.dm b/modular_doppler/wargaming/code/unit_stats/cruiser.dm new file mode 100644 index 00000000000000..625b52b030c14b --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/cruiser.dm @@ -0,0 +1,59 @@ +/datum/wargame_unit_stats/cruiser + abstract_type = /datum/wargame_unit_stats/cruiser + generates_name = TRUE + conditions_limit = 6 + possible_conditions = list( + /datum/wargame_condition/hull_damage, + /datum/wargame_condition/engine_damage, + /datum/wargame_condition/reactor_damage, + ) + is_small_vessel = FALSE + unit_size = WARGAME_SIZE_LARGE + +/datum/wargame_unit_stats/cruiser/im_boutta_blow(obj/structure/wargame_hologram/hologram) + hologram.visible_message(span_warning("[hologram] starts to drift off course before shattering into a large debris field!")) + return ..() + +/datum/wargame_unit_stats/cruiser/mass_driver + unit_class = "mass driver cruiser" + unit_description = "A cruiser equipped with a heavy mass driver." + armor_class = 13 + evasion_modifier = 0 + movement_cost = 1 + maximum_action_points = 4 + weaponry = list( + /datum/wargame_weapon/pd_beam, + /datum/wargame_weapon/missile/swarm, + /datum/wargame_weapon/mass_driver, + /datum/wargame_weapon/autocannon, + ) + +/datum/wargame_unit_stats/cruiser/artillery + unit_class = "artillery cruiser" + unit_description = "A cruiser equipped with heavy long-range artillery." + armor_class = 13 + evasion_modifier = 0 + movement_cost = 1 + maximum_action_points = 4 + weaponry = list( + /datum/wargame_weapon/pd_beam, + /datum/wargame_weapon/missile/swarm, + /datum/wargame_weapon/large_cannon, + /datum/wargame_weapon/medium_cannon, + ) + +/datum/wargame_unit_stats/cruiser/linebreaker + unit_class = "battlecruiser" + unit_description = "A heavily armed and armored cruiser for breaking through fortified enemy lines." + armor_class = 15 + evasion_modifier = 0 + movement_cost = 2 + maximum_action_points = 4 + weaponry = list( + /datum/wargame_weapon/missile/swarm, + /datum/wargame_weapon/missile/torpedo, + /datum/wargame_weapon/medium_cannon, + /datum/wargame_weapon/pdc, + /datum/wargame_weapon/autocannon, + /datum/wargame_weapon/anti_ship_beam, + ) diff --git a/modular_doppler/wargaming/code/unit_stats/destroyer.dm b/modular_doppler/wargaming/code/unit_stats/destroyer.dm new file mode 100644 index 00000000000000..70241476838690 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/destroyer.dm @@ -0,0 +1,54 @@ +/datum/wargame_unit_stats/destroyer + abstract_type = /datum/wargame_unit_stats/destroyer + generates_name = TRUE + conditions_limit = 4 + possible_conditions = list( + /datum/wargame_condition/hull_damage, + /datum/wargame_condition/engine_damage, + /datum/wargame_condition/reactor_damage, + ) + is_small_vessel = FALSE + unit_size = WARGAME_SIZE_MEDIUM + +/datum/wargame_unit_stats/destroyer/im_boutta_blow(obj/structure/wargame_hologram/hologram) + hologram.visible_message(span_warning("[hologram] starts to drift off course before shattering into a large debris field!")) + return ..() + +/datum/wargame_unit_stats/destroyer/brawler + unit_class = "destroyer" + unit_description = "A heavily armored destroyer for close-in brawling engagements." + armor_class = 13 + evasion_modifier = 0 + movement_cost = 1 + maximum_action_points = 4 + weaponry = list( + /datum/wargame_weapon/autocannon, + /datum/wargame_weapon/missile/swarm, + /datum/wargame_weapon/medium_cannon, + ) + +/datum/wargame_unit_stats/destroyer/beam + unit_class = "beam frigate" + unit_description = "A frigate equipped with special anti-ship beam weaponry." + armor_class = 10 + evasion_modifier = 0 + movement_cost = 1 + maximum_action_points = 4 + weaponry = list( + /datum/wargame_weapon/missile/swarm, + /datum/wargame_weapon/anti_ship_beam, + /datum/wargame_weapon/pd_beam, + ) + +/datum/wargame_unit_stats/destroyer/missile + unit_class = "missile frigate" + unit_description = "A frigate loaded with lines and lines of launch tubes for various missiles." + armor_class = 10 + evasion_modifier = 0 + movement_cost = 1 + maximum_action_points = 4 + weaponry = list( + /datum/wargame_weapon/pdc, + /datum/wargame_weapon/missile/cruise, + /datum/wargame_weapon/missile/swarm, + ) diff --git a/modular_doppler/wargaming/code/unit_stats/missiles.dm b/modular_doppler/wargaming/code/unit_stats/missiles.dm new file mode 100644 index 00000000000000..656c0253e3c844 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/missiles.dm @@ -0,0 +1,58 @@ +/datum/wargame_unit_stats/missile + abstract_type = /datum/wargame_unit_stats/missile + talkative = FALSE + +/datum/wargame_unit_stats/missile/im_boutta_blow(obj/structure/wargame_hologram/hologram) + hologram.visible_message(span_warning("[hologram] is reduced to data with a bright spark!")) + return ..() + +/datum/wargame_unit_stats/missile/cruise + unit_class = "cruise missile" + unit_description = "A long range cruise missile." + conditions_limit = 0 // Anything will instantly destroy this + possible_conditions = list( + /datum/wargame_condition/missile_failure, + ) + armor_class = 4 + evasion_modifier = 1 + movement_cost = 1 + maximum_action_points = 2 + is_small_vessel = TRUE + unit_size = WARGAME_SIZE_SMALL + weaponry = list( + /datum/wargame_weapon/ramming/cruise, + ) + +/datum/wargame_unit_stats/missile/swarm + unit_class = "swarm missiles" + unit_description = "A swarm of small interceptor missiles." + conditions_limit = 0 // Anything will instantly destroy this + possible_conditions = list( + /datum/wargame_condition/missile_failure, + ) + armor_class = 2 + evasion_modifier = 2 + movement_cost = 1 + maximum_action_points = 2 + is_small_vessel = TRUE + unit_size = WARGAME_SIZE_SMALL + weaponry = list( + /datum/wargame_weapon/ramming/swarm, + ) + +/datum/wargame_unit_stats/missile/torpedo + unit_class = "torpedo" + unit_description = "A large anti-capital ship torpedo." + conditions_limit = 0 // Anything will instantly destroy this + possible_conditions = list( + /datum/wargame_condition/missile_failure, + ) + armor_class = 5 + evasion_modifier = 0 + movement_cost = 1 + maximum_action_points = 2 + is_small_vessel = TRUE + unit_size = WARGAME_SIZE_SMALL + weaponry = list( + /datum/wargame_weapon/ramming/torpedo, + ) diff --git a/modular_doppler/wargaming/code/unit_stats/static.dm b/modular_doppler/wargaming/code/unit_stats/static.dm new file mode 100644 index 00000000000000..d5fe06c9b7042a --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/static.dm @@ -0,0 +1,60 @@ +/datum/wargame_unit_stats/platform + abstract_type = /datum/wargame_unit_stats/platform + generates_name = FALSE + conditions_limit = 4 + possible_conditions = list( + /datum/wargame_condition/hull_damage, + /datum/wargame_condition/reactor_damage, + ) + is_small_vessel = FALSE + unit_size = WARGAME_SIZE_MEDIUM + +/datum/wargame_unit_stats/platform/get_unit_basic_actions() + var/static/our_actions = list( + WARGAME_UNIT_ATTACK = image(icon = WARGAME_ACTIONS_FILE, icon_state = "unit_attack"), + ) + return our_actions + +/datum/wargame_unit_stats/platform/im_boutta_blow(obj/structure/wargame_hologram/hologram) + hologram.visible_message(span_warning("[hologram] briefly becomes a new star in the night sky, before fading into a large debris field!")) + return ..() + +/datum/wargame_unit_stats/platform/outpost + unit_class = "outpost platform" + unit_description = "A stationary defence platform that trades movement for armor and armament." + armor_class = 11 + evasion_modifier = 0 + movement_cost = 1000 + maximum_action_points = 2 + weaponry = list( + /datum/wargame_weapon/autocannon, + /datum/wargame_weapon/rockets, + /datum/wargame_weapon/pdc, + ) + +/datum/wargame_unit_stats/platform/civilian + unit_class = "civilian station" + unit_description = "A civilian station, only lightly armed to deter asteroids and brigands." + armor_class = 7 + evasion_modifier = 0 + movement_cost = 1000 + maximum_action_points = 2 + weaponry = list( + /datum/wargame_weapon/pdc, + /datum/wargame_weapon/pd_beam, + ) + +/datum/wargame_unit_stats/platform/citadel + unit_class = "military station" + unit_description = "An armed military station, highly dangerous even without outside support." + armor_class = 15 + evasion_modifier = 0 + movement_cost = 1000 + maximum_action_points = 4 + weaponry = list( + /datum/wargame_weapon/mass_driver, + /datum/wargame_weapon/medium_cannon, + /datum/wargame_weapon/pd_beam, + /datum/wargame_weapon/missile/cruise, + /datum/wargame_weapon/missile/swarm, + ) diff --git a/modular_doppler/wargaming/code/unit_stats/strike.dm b/modular_doppler/wargaming/code/unit_stats/strike.dm new file mode 100644 index 00000000000000..d7ea944f3fc2a7 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/strike.dm @@ -0,0 +1,43 @@ +/datum/wargame_unit_stats/strike + abstract_type = /datum/wargame_unit_stats/strike + generates_name = FALSE + conditions_limit = 2 + possible_conditions = list( + /datum/wargame_condition/hull_damage, + /datum/wargame_condition/engine_damage, + /datum/wargame_condition/reactor_damage, + ) + is_small_vessel = TRUE + unit_size = WARGAME_SIZE_SMALL + +/datum/wargame_unit_stats/strike/im_boutta_blow(obj/structure/wargame_hologram/hologram) + hologram.visible_message(span_warning("[hologram] starts to careen off course before crackling and exploding!")) + return ..() + +/datum/wargame_unit_stats/strike/wing + unit_class = "strike wing" + unit_description = "A strike wing of bombers and its fighter escorts." + armor_class = 7 + evasion_modifier = 4 + movement_cost = 1 + maximum_action_points = 5 + weaponry = list( + /datum/wargame_weapon/pdc, + /datum/wargame_weapon/rockets, + /datum/wargame_weapon/bombs, + /datum/wargame_weapon/ramming/strike, + ) + +/datum/wargame_unit_stats/strike/interceptor + unit_class = "interceptor wing" + unit_description = "An interceptor wing for interdicting other strike wings or incoming missiles." + armor_class = 6 + evasion_modifier = 5 + movement_cost = 1 + maximum_action_points = 5 + weaponry = list( + /datum/wargame_weapon/pdc, + /datum/wargame_weapon/autocannon, + /datum/wargame_weapon/missile/swarm, + /datum/wargame_weapon/ramming/strike, + ) diff --git a/modular_doppler/wargaming/code/unit_weapons.dm b/modular_doppler/wargaming/code/unit_weapons.dm deleted file mode 100644 index c656ac70a0022f..00000000000000 --- a/modular_doppler/wargaming/code/unit_weapons.dm +++ /dev/null @@ -1,197 +0,0 @@ -/datum/wargame_weapon - abstract_type = /datum/wargame_weapon - /// The name of the weapon - var/weapon_name - /// The attack roll of the weapon if within range - var/attack_roll - /// A negative number to make weapons that are weak to armor less effective against high armor class - var/damage_roll_bonus - /// The weapon range in tiles, 0 is the same tile as the ship firing it - var/attack_range - /// If this attack can be evaded, giving ships with an evasion bonus extra armor class - var/evadable - /// If this weapon has roll disadvantage against small ships - var/small_ship_disadvantage - /// The icon_state to use for this weapon's radial menu option - var/radial_icon_state - /// If this weapon has a limited number of uses before needing to be re-armed - var/maximum_ammo - /// How many action points this weapon costs to fire - var/action_point_cost - /// If this weapon's attack is handled entirely by special_effects_fire() - var/all_special_effects = FALSE - -/// Returns a string to describe the weapon -/datum/wargame_weapon/proc/weapon_description() - return - -/// By default, checks in a circle range around the ship for another hologram to target -/datum/wargame_weapon/proc/pick_target(mob/living/user, obj/structure/wargame_hologram/hologram) - var/datum/wargaming_team/hologram_team = hologram.team_reference?.resolve() - var/list/potential_targets = list() - for(var/obj/structure/wargame_hologram/other_hologram in range(attack_range)) - if(!other_hologram.unit_stats.can_be_a_target) - continue - if(isnull(hologram_team)) - potential_targets += other_hologram - continue // If we have no team then free for all everything's a target - var/datum/wargaming_team/other_hologram_team = other_hologram.team_reference?.resolve() - if(other_hologram_team == hologram_team) - continue // Cannot target friendlies otherwise - potential_targets += other_hologram - if(!length(potential_targets)) - hologram.balloon_alert(user, "no targets!") - return - var/picked_target = tgui_input_list(user, "Pick a target within range.", "Target Picker", potential_targets) - if(isnull(picked_target)) - hologram.balloon_alert(user, "no choice!") - return - return picked_target - -/// If this weapon has any special pre-fire checks -/datum/wargame_weapon/proc/prefire_checks(mob/living/user, datum/wargame_unit_stats/stats, obj/structure/wargame_hologram/hologram) - if(stats.action_points < action_point_cost) - hologram.balloon_alert(user, "not enough AP!") - return FALSE - if(isnull(maximum_ammo)) - return TRUE - if(maximum_ammo <= 0) - hologram.balloon_alert(user, "no ammo!") - return FALSE - return TRUE - -/// If this weapon does anything special when initially firing -/datum/wargame_weapon/proc/special_effects_fire(mob/living/user, obj/structure/wargame_hologram/hologram) - if(isnull(maximum_ammo)) - return - maximum_ammo-- - -/datum/wargame_weapon/mass_driver - weapon_name = "Mass Driver" - attack_roll = "2d10+4" - damage_roll_bonus = 0 - attack_range = 3 - evadable = TRUE - small_ship_disadvantage = TRUE - radial_icon_state = "weapon_guns_big" - action_point_cost = 1 - -/datum/wargame_weapon/mass_driver/weapon_description() - return "A large mass driver for launching hardened cores at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." - -/datum/wargame_weapon/large_cannon - weapon_name = "7\" Cannon" - attack_roll = "2d16+4" - damage_roll_bonus = 0 - attack_range = 2 - evadable = TRUE - small_ship_disadvantage = TRUE - radial_icon_state = "weapon_guns_big" - action_point_cost = 1 - -/datum/wargame_weapon/large_cannon/weapon_description() - return "A large calibre 7 in. cannon for launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." - -/datum/wargame_weapon/medium_cannon - weapon_name = "2.5\" Cannon" - attack_roll = "4d5+4" - damage_roll_bonus = 0 - attack_range = 2 - evadable = TRUE - small_ship_disadvantage = TRUE - radial_icon_state = "weapon_guns_big" - action_point_cost = 1 - -/datum/wargame_weapon/medium_cannon/weapon_description() - return "A medium calibre 2.5 in. cannon fore launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." - -/datum/wargame_weapon/rockets - weapon_name = "Rockets" - attack_roll = "6d4" - damage_roll_bonus = 0 - attack_range = 2 - evadable = TRUE - radial_icon_state = "weapon_rocket" - action_point_cost = 1 - -/datum/wargame_weapon/rockets/strike - maximum_ammo = 2 - -/datum/wargame_weapon/rockets/weapon_description() - return "A barrage of unguided rockets from a fixed rack or large launcher. Max range of [attack_range] tiles. [action_point_cost] AP to fire." - -/datum/wargame_weapon/bombs - weapon_name = "Bombs" - attack_roll = "1d20" - damage_roll_bonus = 0 - attack_range = 1 - evadable = TRUE - small_ship_disadvantage = TRUE - radial_icon_state = "weapon_bomb" - maximum_ammo = 2 - action_point_cost = 1 - -/datum/wargame_weapon/bombs/weapon_description() - return "A group of unpowered explosive bombs to crack large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." - -/datum/wargame_weapon/minefield - weapon_name = "Mines" - attack_roll = "3d8" - damage_roll_bonus = 0 - attack_range = 0 - evadable = TRUE - radial_icon_state = "weapon_mine" - maximum_ammo = 6 - action_point_cost = 2 - -/datum/wargame_weapon/minefield/weapon_description() - return "A dense field of mines that only allow friendly signals to pass. Will only damage ships that pass through them. [action_point_cost] AP to fire." - -/datum/wargame_weapon/autocannon - weapon_name = "45mm Autocannon" - attack_roll = "2d10+2" - damage_roll_bonus = 0 - attack_range = 1 - evadable = TRUE - radial_icon_state = "weapon_guns" - action_point_cost = 1 - -/datum/wargame_weapon/autocannon/weapon_description() - return "Low calibre 45mm autocannons for fast moving targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." - -/datum/wargame_weapon/pdc - weapon_name = "26mm PDC" - attack_roll = "1d12+8" - damage_roll_bonus = -2 - attack_range = 1 - evadable = FALSE - radial_icon_state = "weapon_guns_pdc" - action_point_cost = 1 - -/datum/wargame_weapon/pdc/weapon_description() - return "Small calibre 26mm autocannons with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." - -/datum/wargame_weapon/anti_ship_beam - weapon_name = "Anti-Ship Beam" - attack_roll = "1d20+5" - damage_roll_bonus = 0 - attack_range = 1 - evadable = FALSE - small_ship_disadvantage = TRUE - radial_icon_state = "weapon_beam" - action_point_cost = 2 - -/datum/wargame_weapon/anti_ship_beam/weapon_description() - return "A high-energy beam for coring large ships with. Max range of [attack_range] tiles. [action_point_cost] AP to fire." - -/datum/wargame_weapon/pd_beam - weapon_name = "PD Beam" - attack_roll = "1d20+6" - damage_roll_bonus = -4 - attack_range = 2 - evadable = FALSE - radial_icon_state = "weapon_pd_beam" - action_point_cost = 1 - -/datum/wargame_weapon/pd_beam/weapon_description() - return "Highly focused beams with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." diff --git a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm new file mode 100644 index 00000000000000..ca6ac95f828c08 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm @@ -0,0 +1,109 @@ +/datum/wargame_weapon + abstract_type = /datum/wargame_weapon + /// The name of the weapon + var/weapon_name + /// The attack roll of the weapon if within range + var/attack_roll + /// A negative number to make weapons that are weak to armor less effective against high armor class + var/damage_roll_bonus + /// The weapon range in tiles, 0 is the same tile as the ship firing it + var/attack_range + /// If this attack can be evaded, giving ships with an evasion bonus extra armor class + var/evadable + /// If this weapon has roll disadvantage against small ships + var/small_ship_disadvantage = FALSE + /// The icon_state to use for this weapon's radial menu option + var/radial_icon_state + /// If this weapon has a limited number of uses before needing to be re-armed + var/maximum_ammo + /// How many action points this weapon costs to fire + var/action_point_cost + /// If this weapon's attack is handled entirely by special_effects_fire() + var/all_special_effects = FALSE + +/// Returns a string to describe the weapon +/datum/wargame_weapon/proc/weapon_description() + return + +/// Returns a string for a ship to say when firing the weapon +/datum/wargame_weapon/proc/firing_voiceline() + return + +/// Plays a firing sound for the weapon +/datum/wargame_weapon/proc/weapon_firing_sound(obj/firer) + return + +/// By default, checks in a circle range around the ship for another hologram to target +/datum/wargame_weapon/proc/pick_target(mob/living/user, obj/structure/wargame_hologram/hologram) + var/datum/wargaming_team/hologram_team = hologram.team_reference?.resolve() + var/list/potential_targets = list() + for(var/obj/structure/wargame_hologram/other_hologram in view(attack_range, hologram)) + if(!other_hologram.unit_stats.can_be_a_target) + continue + if(isnull(hologram_team)) + potential_targets += other_hologram + continue // If we have no team then free for all everything's a target + var/datum/wargaming_team/other_hologram_team = other_hologram.team_reference?.resolve() + if(other_hologram_team == hologram_team) + continue // Cannot target friendlies otherwise + potential_targets += other_hologram + if(!length(potential_targets)) + hologram.balloon_alert(user, "no targets!") + return + var/picked_target = tgui_input_list(user, "Pick a target within range.", "Target Picker", potential_targets) + if(isnull(picked_target)) + hologram.balloon_alert(user, "no choice!") + return + return picked_target + +/// If this weapon has any special pre-fire checks +/datum/wargame_weapon/proc/prefire_checks(mob/living/user, datum/wargame_unit_stats/stats, obj/structure/wargame_hologram/hologram) + if(stats.action_points < action_point_cost) + hologram.balloon_alert(user, "not enough AP!") + return FALSE + if(isnull(maximum_ammo)) + return TRUE + if(maximum_ammo <= 0) + hologram.balloon_alert(user, "no ammo!") + return FALSE + return TRUE + +/// If this weapon does anything special when initially firing +/datum/wargame_weapon/proc/special_effects_fire(mob/living/user, datum/wargame_unit_stats/stats, obj/structure/wargame_hologram/hologram, obj/structure/wargame_hologram/target_hologram, impact) + stats.action_points -= action_point_cost + if(isnull(maximum_ammo)) + return + maximum_ammo-- + +// Generic ramming weapon for missiles +/datum/wargame_weapon/ramming + abstract_type = /datum/wargame_weapon/ramming + weapon_name = "Ramming Speed" + attack_range = 1 + radial_icon_state = "weapon_ram" + +/datum/wargame_weapon/ramming/special_effects_fire(mob/living/user, datum/wargame_unit_stats/stats, obj/structure/wargame_hologram/hologram, obj/structure/wargame_hologram/target_hologram, impact) + if(impact) + stats.im_boutta_blow(hologram) + +// Generic missile launch weapon +/datum/wargame_weapon/missile + abstract_type = /datum/wargame_weapon/missile + attack_range = 7 + all_special_effects = TRUE + /// The type of missile hologram this should spawn when running an attack outside of short range + var/obj/structure/wargame_hologram/missile_type + +/datum/wargame_weapon/missile/special_effects_fire(mob/living/user, datum/wargame_unit_stats/stats, obj/structure/wargame_hologram/hologram, obj/structure/wargame_hologram/target_hologram, impact) + if(!target_hologram) + return // ?? + if(get_dist(hologram, target_hologram) <= 1) + target_hologram.unit_stats.get_attacked(user, hologram, src) + return ..() + var/dir_to_target = get_dir(hologram, target_hologram) + var/obj/structure/wargame_hologram/new_missile = new missile_type(get_turf(hologram), hologram.projector) + new_missile.team_reference = hologram.team_reference + new_missile.color = hologram.color + new_missile.update_appearance() + try_move_adjacent(new_missile, dir_to_target) + return ..() diff --git a/modular_doppler/wargaming/code/unit_weapons/beams.dm b/modular_doppler/wargaming/code/unit_weapons/beams.dm new file mode 100644 index 00000000000000..197cf2d2c3889c --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/beams.dm @@ -0,0 +1,46 @@ +/datum/wargame_weapon/anti_ship_beam + weapon_name = "Anti-Ship Beam" + attack_roll = "1d20+5" + damage_roll_bonus = 0 + attack_range = 1 + evadable = FALSE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_beam" + action_point_cost = 2 + +/datum/wargame_weapon/anti_ship_beam/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/beam.ogg', 75, TRUE) + +/datum/wargame_weapon/anti_ship_beam/weapon_description() + return "A high-energy beam for coring large ships with. Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/anti_ship_beam/firing_voiceline() + var/static/list/lines = list( + "Try not to stare directly at the beam.", + "Hope you brought your sunglasses today.", + "Plasma cutters got nothing on me, watch this.", + ) + return pick(lines) + +/datum/wargame_weapon/pd_beam + weapon_name = "PD Beam" + attack_roll = "1d20+6" + damage_roll_bonus = -5 + attack_range = 2 + evadable = FALSE + radial_icon_state = "weapon_pd_beam" + action_point_cost = 1 + +/datum/wargame_weapon/pd_beam/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/beam.ogg', 75, TRUE) + +/datum/wargame_weapon/pd_beam/weapon_description() + return "Highly focused beams with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/pd_beam/firing_voiceline() + var/static/list/lines = list( + "Incoming track within range, blink it with the lasers.", + "Get the spotlights on that track, I want it off the board!", + "Laser defence responding to incoming track.", + ) + return pick(lines) diff --git a/modular_doppler/wargaming/code/unit_weapons/big_guns.dm b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm new file mode 100644 index 00000000000000..c8b19f4adc76be --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm @@ -0,0 +1,73 @@ +/datum/wargame_weapon/mass_driver + weapon_name = "Mass Driver" + attack_roll = "2d10+4" + damage_roll_bonus = 0 + attack_range = 3 + evadable = TRUE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_guns_big" + action_point_cost = 1 + +/datum/wargame_weapon/mass_driver/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 75, TRUE) + +/datum/wargame_weapon/mass_driver/weapon_description() + return "A large mass driver for launching hardened cores at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/mass_driver/firing_voiceline() + var/static/list/lines = list( + "We shot this in atmosphere once, you should've seen it!", + "One-second delivery, or your tungsten rod is free!", + "Let's see how that ship looks with a brand new hole down the center of it.", + "Tungsten cube, hot and ready!", + "A few more shots like this, and we'll have to change the rails out.", + ) + return pick(lines) + +/datum/wargame_weapon/large_cannon + weapon_name = "7\" Cannon" + attack_roll = "2d16+4" + damage_roll_bonus = 0 + attack_range = 3 + evadable = TRUE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_guns_big" + action_point_cost = 1 + +/datum/wargame_weapon/large_cannon/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 75, TRUE) + +/datum/wargame_weapon/large_cannon/weapon_description() + return "A large calibre 7 in. cannon for launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/large_cannon/firing_voiceline() + var/static/list/lines = list( + "Target lead and trajectory calculations green, fire at will.", + "Artillery adds dignity to what would otherwise be a vulgar brawl", + "Shell, away. Have we ceased life, saer?", + ) + return pick(lines) + +/datum/wargame_weapon/medium_cannon + weapon_name = "2.5\" Cannon" + attack_roll = "4d5+4" + damage_roll_bonus = 0 + attack_range = 2 + evadable = TRUE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_guns_big" + action_point_cost = 1 + +/datum/wargame_weapon/medium_cannon/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 75, TRUE) + +/datum/wargame_weapon/medium_cannon/weapon_description() + return "A medium calibre 2.5 in. cannon fore launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/medium_cannon/firing_voiceline() + var/static/list/lines = list( + "All mounts local control, watch fire for friendlies!", + "I want all guns on that track, open fire!", + "Target in range, all guns to local fire director!", + ) + return pick(lines) diff --git a/modular_doppler/wargaming/code/unit_weapons/bombs.dm b/modular_doppler/wargaming/code/unit_weapons/bombs.dm new file mode 100644 index 00000000000000..bbe275e9fdb3ec --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/bombs.dm @@ -0,0 +1,44 @@ +/datum/wargame_weapon/rockets + weapon_name = "Rockets" + attack_roll = "6d4" + damage_roll_bonus = 0 + attack_range = 2 + evadable = TRUE + radial_icon_state = "weapon_rocket" + action_point_cost = 1 + +/datum/wargame_weapon/rockets/strike + maximum_ammo = 2 + +/datum/wargame_weapon/rockets/weapon_description() + return "A barrage of unguided rockets from a fixed rack or large launcher. Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/rockets/firing_voiceline() + var/static/list/lines = list( + "Go for rocket salvo, let's see how they like some of these!", + "Rockets! Rockets! You're in my party now!", + "Make it rain on them!", + ) + return pick(lines) + +/datum/wargame_weapon/bombs + weapon_name = "Bombs" + attack_roll = "1d20" + damage_roll_bonus = 0 + attack_range = 1 + evadable = TRUE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_bomb" + maximum_ammo = 1 + action_point_cost = 1 + +/datum/wargame_weapon/bombs/weapon_description() + return "A group of unpowered explosive bombs to crack large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/bombs/firing_voiceline() + var/static/list/lines = list( + "I got a present for ya!", + "Kill him! Kill him! Point the nose at him and drop the bomb at him!", + "Bombs away- Hard burn we're in the blast zone!", + ) + return pick(lines) diff --git a/modular_doppler/wargaming/code/unit_weapons/missiles.dm b/modular_doppler/wargaming/code/unit_weapons/missiles.dm new file mode 100644 index 00000000000000..40e4902b7b4832 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/missiles.dm @@ -0,0 +1,111 @@ +/datum/wargame_weapon/missile/cruise + weapon_name = "Cruise Missile" + attack_roll = "1d20+4" + damage_roll_bonus = 0 + evadable = TRUE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_missile" + action_point_cost = 1 + maximum_ammo = 6 + missile_type = /obj/structure/wargame_hologram/controllable/cruise_missile + +/datum/wargame_weapon/missile/cruise/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/missile_launch.ogg', 50, TRUE) + +/datum/wargame_weapon/missile/cruise/weapon_description() + return "A long range cruise missile for engaging large targets. Becomes a controllable ship if the target is beyond short range. \ + Max target range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/missile/cruise/firing_voiceline() + var/static/list/lines = list( + "Drive signature? Ask them to hold this cruise missile for us.", + "Drive cone locked, cruise missile away.", + "Lock confirmed, launching cruise missile.", + ) + return pick(lines) + +/datum/wargame_weapon/ramming/cruise + weapon_name = "Cruise Missile Impact" + attack_roll = "1d20+4" + damage_roll_bonus = 0 + evadable = TRUE + small_ship_disadvantage = TRUE + action_point_cost = 1 + +/datum/wargame_weapon/ramming/cruise/weapon_description() + return "Guide the cruise missile into a target. This is a one way trip. \ + Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/missile/swarm + weapon_name = "Swarm Missiles" + attack_roll = "5d4+2" + damage_roll_bonus = -2 + evadable = FALSE + radial_icon_state = "weapon_missile_small" + action_point_cost = 1 + maximum_ammo = 12 + missile_type = /obj/structure/wargame_hologram/controllable/swarm_missile + +/datum/wargame_weapon/missile/swarm/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/swarm_launch.ogg', 75, TRUE) + +/datum/wargame_weapon/missile/swarm/weapon_description() + return "A swarm of small missiles for intercepting other missiles or strike craft. Becomes a controllable ship if the target is beyond short range. \ + Max target range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/missile/swarm/firing_voiceline() + var/static/list/lines = list( + "Get some swarmers on that track!", + "Try doding the third, fourth, and fifth ones this time!", + "Solid track, swarmers out", + ) + return pick(lines) + +/datum/wargame_weapon/ramming/swarm + weapon_name = "Swarm Missiles Impact" + attack_roll = "5d4+2" + damage_roll_bonus = -2 + evadable = FALSE + action_point_cost = 1 + +/datum/wargame_weapon/ramming/swarm/weapon_description() + return "Guide the swarm missiles into a target. This is a one way trip. \ + Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/missile/torpedo + weapon_name = "Torpedo" + attack_roll = "2d20+4" + damage_roll_bonus = 0 + evadable = TRUE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_torpedo" + action_point_cost = 1 + maximum_ammo = 4 + missile_type = /obj/structure/wargame_hologram/controllable/torpedo + +/datum/wargame_weapon/missile/torpedo/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/missile_launch.ogg', 50, TRUE) + +/datum/wargame_weapon/missile/torpedo/weapon_description() + return "A beefy torpedo for cracking large ships in two. Becomes a controllable ship if the target is beyond short range. \ + Max target range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/missile/torpedo/firing_voiceline() + var/static/list/lines = list( + "The guns ain't enough. I want that track gone.", + "Torpedo away, stand by for fireworks.", + "Good track confirmed, torpedo dispensed.", + ) + return pick(lines) + +/datum/wargame_weapon/ramming/torpedo + weapon_name = "Torpedo Impact" + attack_roll = "2d20+4" + damage_roll_bonus = 0 + evadable = TRUE + small_ship_disadvantage = TRUE + action_point_cost = 1 + +/datum/wargame_weapon/ramming/torpedo/weapon_description() + return "Guide the torpedo into a target. This is a one way trip. \ + Max range of [attack_range] tiles. [action_point_cost] AP to fire." diff --git a/modular_doppler/wargaming/code/unit_weapons/ramming.dm b/modular_doppler/wargaming/code/unit_weapons/ramming.dm new file mode 100644 index 00000000000000..30ea425d069da0 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/ramming.dm @@ -0,0 +1,15 @@ +/datum/wargame_weapon/ramming/strike + attack_roll = "5d5+2" + damage_roll_bonus = 0 + evadable = TRUE + small_ship_disadvantage = TRUE + action_point_cost = 2 + +/datum/wargame_weapon/ramming/strike/firing_voiceline() + var/static/list/lines = list( + "One. Last. Plan!", + "It's been an honor.", + "Initiating burn drive, godspeed.", + "Ah.", + ) + return pick(lines) diff --git a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm new file mode 100644 index 00000000000000..985c710becbd45 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm @@ -0,0 +1,45 @@ +/datum/wargame_weapon/autocannon + weapon_name = "45mm Autocannon" + attack_roll = "2d10+2" + damage_roll_bonus = 0 + attack_range = 1 + evadable = TRUE + radial_icon_state = "weapon_guns" + action_point_cost = 1 + +/datum/wargame_weapon/autocannon/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/autocannon.ogg', 75, TRUE) + +/datum/wargame_weapon/autocannon/weapon_description() + return "Low calibre 45mm autocannons for fast moving targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/autocannon/firing_voiceline() + var/static/list/lines = list( + "Tracking looks good, let's let 'em have some.", + "Autocannon belts spooled, ready to fire.", + "Like to see them dodge this one, shells away.", + ) + return pick(lines) + +/datum/wargame_weapon/pdc + weapon_name = "26mm PDC" + attack_roll = "1d12+8" + damage_roll_bonus = -5 + attack_range = 1 + evadable = FALSE + radial_icon_state = "weapon_guns_pdc" + action_point_cost = 1 + +/datum/wargame_weapon/pdc/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/autocannon.ogg', 75, TRUE) + +/datum/wargame_weapon/pdc/weapon_description() + return "Small calibre 26mm autocannons with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/pdc/firing_voiceline() + var/static/list/lines = list( + "We need PDC on that target, now!", + "One wall of lead, coming right up.", + "Tracking multiple targets, open fire.", + ) + return pick(lines) diff --git a/modular_doppler/wargaming/icons/actions.dmi b/modular_doppler/wargaming/icons/actions.dmi index 2b85178ae000a598f9c7b2d1f4336f0ff3af683f..89a23e4cb9ffe4f608380923335ee8bd81b6a804 100644 GIT binary patch delta 4296 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 T;g%am@b74;>#2QIv5ou>oXGAE diff --git a/modular_doppler/wargaming/icons/holograms.dmi b/modular_doppler/wargaming/icons/holograms.dmi new file mode 100644 index 0000000000000000000000000000000000000000..7679aadedf805f519fbde34b95af29f252111c06 GIT binary patch literal 2420 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 VCt;`lh9_74kA8C29X|;0{{XJ+4F&)J literal 0 HcmV?d00001 diff --git a/modular_doppler/wargaming/sound/beam.ogg b/modular_doppler/wargaming/sound/beam.ogg new file mode 100644 index 0000000000000000000000000000000000000000..def94ad0b286935ed26fa14bbdbfbee9d3954906 GIT binary patch literal 14151 zcmeHuc|4Te`~N){gb_m48f$h&jHMJ~pU4<9w&#hg*;STGw!$P^S*Ax+b}>knp~aFY zEriS@N|KO-N46sT&Wt|K_xXHZpVzO?>-G8k^?Ut}d#<_fbIyIveXi?V*Y&=xd)OR1 zW(ToAKZ~VC1)g#0cd*O~whI;>eA3-Fgi*m_QVBtupg@Z-=Jy?#4dc$g3gb=~xZ;s; zP8V=v^!$6-&$=~~Ht6B#d)!CO=HxMCfUmnfa{{C#Qd0w^tFae_Mk*W*K7~YSXviVG zy-s2SNFjI#P#1#K z@)Uv~HYiq+Gw0-c%;)BnU7{2Gwqi=1jI)G`8d;(V2IL{G!zz^*XW_A`ube|> zn>mI<=8V1}kucw5UtLa~4$kY&2y^BRfnK8fymi5`AA zTLHN~P1Sz9w%uL>BEghSAUY6_961$84-ceY2^_Fzp4bOEH3Yu-=YzR)Bn--z(aVrg z%#c>h7}L#QizijGK%NYi85?ONR#u;@IG_p*u|W(t2v*_Z*VcYKrqmSQQn_U znmnC$>`Vre=G^qnDgAd3L!T!~kRjk@=Lw0e!~CQy2b053ARtpiylm%574s0S&XE1) zp(dT7R!_4CPpQ9i|3e5s?4TI#P%*XJ-~YbMYkvEU;zr#EMzTaPzqJilBpx4-QoA$QN+^6cD@ z7kZP|0$zxYT?=`k`0VPV{Hwh=AulX>a!R(&&IsgkRF%I3b)u8kcpr6t=Zm*#U72B~a+p+9}QozIvDRsRyJqD{B= zNTp8P2)U(a;a@_1<*Zx+Hn?KRmPP@gB0Vn~v5PYGgOq&|Xhr^cuD=WJPuU)R$-ciG zJ@XP^GjHu@=4K{3gaBrm5c|8#6E9jpXms1s8iv)ofq zo!8lgO2Lm%na z_Ub2k(Zjt48@zsNjB;v-8d>?im7t(P`t@z0u*_CYsI#xg_JDMOyAIptSx zOqCb@FU`sKNxGSxbTct&GBHiz;uYVEIh4DF0nctt-}~k9m*xP40f#AG*7Tn==K)H> z7${GziuJX%-liPnvLBD*Rl*teWefhfe=36p)!B72Ax$dOSj`=Ray==s9d6=e8Q>%mto!-f4n zum7VjS!-6U9>(!=6flvEAPgK^*AKq#f3U;g!4A{^Xp)~dOZ>07z>2^`Jv)BR1y%$m z`Y+A(-+12tKgR#l901!6#tS~em;xzfmRMaFbV1fA18Lc-73=BLtK}nRwFBuStcA#7 z@85Fo3~m^H1r1t=Le9%t+jH2h;}rgaj}X!qG~}?`vRN=1N{#-ZL*TAns~v@$w(-6! z`}^Y+FoJG5|C+I}5EKjJfMTH?&kX;1rb0(y2-3~)a02*3H^U1C#dP&ccOn4uty(V2zubmCfa!W;@(o^lW$ z9D0uveEw3)_M`5V65qln=_-f%P13!5b0)9KYjrPJ@fS{>Dc#ny0M_lBR7ha~?gg+N z&R{#*2dl!1^Lc`Wkh0K&QQa%!X9_=^DfG#qJUgNCe8I$4(AthjB#-%AnY>u2kW)&b zD1nK3m@bM)JmQn{v-+Oe{+^$`kL2rYT{amfK-VH zKsM!q9t@0=m4O04=P-6E8`SM7@$3S1nRtB$__0(h2?bpMw#Iyt2nG1#dI&ED1{rQ~ zJ1O9>h5&)dZXxKwdxp4f>`5y1wah3zb7S&iDa9|Nm|0U!`HP>71}2|Q9{I^lx#xvG z4E9pE8K9Ct97tdQTnc6SVFQD$17U7&022Y19srgB7CvFPZxwC`vN;YxO1Is@;xh|t z9sozj!tj^zC{9Iyxt<`4`7joW377wQ`@>=hpn}i83IHm9ga;ttOklE-amlRvuM&F* z8J@uU;BY*mLgt!aDqL8sRsiDU01`DyM17J4crJm@qu0`+gesyOFA|5tVymdx`zIyh z@y>p#k3Z^7B8zFxDJb~?u#%E1?#VTGgfU8r)K~76PesUhKh*{i|CvW+S=d9U$0Gh3 zk0%(NUpXhE^cw{ZrJx==$MZjaBWV;-meQ8%DvWY$G&tL^*Ii#1T6L~2*SW7@nGx)Aac%fe2N};Fc3HuWa075-2>1d z4x?C7oPwHQHZbt1ucB9%9-{#u)vTBJ>1!e24kmDwE1`k)Y&GDqSXD5T0vL+X573`? zfXnidte3L(7s%5uyL=LbvZcxV#+_6%+T}xwF?RmiykFX+usUP7Gf%s{#_n zvFGid9pX2R$L$DB!b&pZxmZ>RpgdV|LlYCN5DCd$yOC1TGO}{=d$!!pdv<6yfQMXs z{QTV#<`+6SEE1n^T3&p*4GTd?V6H*oL_#na6JRl~VLvk$c8bhah>^Kqc(A#R3sgHD zO2J*zLHcnb+k@w z;jcnmA`pKgeP7yOlv!94){etrN1=o7=l6aX=0vP{c)W=bBKXAvE~K;zAI&wm@Uc|9 zWoBE+YoX|954ww1Nw*Dqq`p(U@Tk(_lqbJ)d_`~huzU&UQyldTf{sjyHxh~8tZ+En z9QnOA>RDGy#L{QSi8qvcD=WGalh0{~<>2M$+M)7dB;9P8P@!pQjJ2%f_efXZPn618 zU5^;Djg?jqNeIrnNBj74&$aBf)E~>1_0Ev2elQC~1o!jnTGelgrX;C^B)EU&x@%RX zk8FxbT+SxFqQs@)(U%%c*OpVk;VvU%*&MXPUT`-)rx}7hAA7n1wZzK)VLE4#v@nK6 znT1~u8DDnMIsABe`O;mD2zcMLj$`9}Ovkt=_HY~5@3oP3!oo;MUD7mpvkHWiQM zEP1FGg1c}1(BM2@f2dX#SeHY!OLIg?gv*Pb+)W~%Sz)@yZ5{eT?U!RK^p^e!SeF@s zmxry+Hjb6G%_z56P(IL)nCtsT(ejXgkV~)U<+hSTI_Vj-xA4j7Ydg|LM)G)y&HDF! zblKjm!<^9IMk17 zVJT!=5!cNpLF)4n(RXd~p}Drsin^L~$BZOq_iUmu8*3zw14O6;zXNf~!oB<=X`d{I zS8HNMJy$5FGhz-+rc?tVN5DX!{F)eR7Xk4 zs?xD!v-PE^Z-q`9)t4@Bw(!5o3)Y=jXia?p`)Kcl7!!92BEHgSNx!~oge^7UEtTOF zmQylkNj!XeG&Ab_%+&(wSf10R^X1RI4@w8Bvc~I|K$-kjWp-Q|qHPlO1_Jim+UuP< zltQvL9SzD>Z_2jQy;uBxD6XJ8S8lSwXosy44t#Vbm8`c+!XYX|J||v#6MZIV&FjvR z;x+NzhnKU?Hea2*w?0zt(!^2>423kI{3Y!iCml8vNRZ1F3pdZDXXpLP(g^PF$$OqdGzB$+s6A(jB*^@i=bmDZa-i?4)%hrfU>3w3v11xPep5{f*%#o*Y z+=}HNkLn(Ewq96Y?b@mCqLsc8J-koUwJGuYk7_>|_ivkdqx{epdwve~b6fP2c!%! zzr8#asLrujYvRGAJKaH>l;XF|Vlhdx-6!BQtQUgElZtm@WgRqX=21+@7;fXc|43F> z2px3q@ZFlj2`Aqp?8eHmflZ+W=z%3*DiGYKo_Lp#683p=P&13MXnqc2`~JnIfXRad z^=Z|hSJ8ibr>_yB7rd962kZ6Mg?(x(?Qf2-SYHW?wNJJvK2PuSMy2q}d`RA@ZWj5j z+O;)$ef7)c=A|vq@NfiV-)VSjgid1R6+gc_MxGo?aY9O?N1X%0=@S+5S}+qScTB;b zQg0JehMh>op)!l(g%vko0qAm8q1A5ruzav;c1yCA?R{Uny*`_tisTzYW`bW#MK@pd zyLa{TtL}^-ZJHSZ-X33u_EQj%H{mcPWf7{jZCTRatak(D8B6JnsdV}~D#ST)l5d~R&K(fNCoeRKtR#BW^ZY784g)J`!1 zAAqQ3rfo2hK-7dl5^E`4Yoxlp$+hWp!BllmkXg#@jg2*S8TZ*3AsMqLog82AatMdZ zTU3H(p2S(nDqIogSbU*pJoEiGd9y~FQSRmToVzcV=N2dL9Y1{vh#J8mLFv6Y8fzTZ zyNn~#+?^$?w1);&I>}=bBi~90aC^kk zr~LKmT~V|=1MO2<(J^(h^y?-r>&1^H7v&0AxK~?b2}g}$)C?b^w?@+1+%)$NDSu;J{%dlp(WI#kg#Zj^p-OQapFGlYCK^A_rGvN+O&V4gdix z*u1T5Y5bb26SIpTAP#fjBQj}VywYH8Lrq0$68~h^&d*Wb_STOke;42QTt^{olCDId zi0vJCy9++ZizTJJeTFB|{$5x`H{+s2p`wvH<36_z8L9Nl{rMxJ;RAVNp3J@06;Os| z0nYDXgVn*8+sK@6G}oUP&7eo~WtGD37yJN@Z`HLqf|#3^r``%&IpQI5zwpH5}Q$5xuZzWL_;_J>{F7-cD9oUIbQ z&Z1@3Fto_sD>7(QC9Eh%?D*_cCt{W|{|M--OA~l9(;YI48$f55rU!uXZftJ0zGT9> zc&NiZihJzxBPNxk?Zk6kdr~jvr=zmUAJ-VXbnk0GHP5tkWwBp-qYm4e;6Pqb&+t6G4ueEzDt;8jha1 zbMp7^fy;prrR(#=#+ktr69%l2^2!&K%rPB3p5wT2i^S6n|B$HP41+~^FTMe3?o^lV zUz^?-(E(_!8GjY7uNW?j9;!@lq9 zsXxipWd^$-WG<{F4wtZ(aOyScKb)SCQKVSf-4;Y^il72rNXq?u=KEJKq5I%`NC*J? z{d04>-WtrgAu~_)oE3TUaXTNSH_H%JQ>=s`^J7>r=W*+RapKcUtK;+v3t0itec=Jy zX;?Y9pdb}*34=c}N`H^DoFVBR-NPA|v+tDuE%iN}X{nb?q|?`zCL)?Qh%7WL0s)gQ zBa4Z#m>kEQ-9{7;;%LA9EbrZ-%bMv*bYHV;=v?k>C+&PWoV5v+;Bi-gW%;pS+BlZ; z^)TGJk9{#FXScWltl5%U&{`Jk z@oC3WAgvX8d)NyY+&kBw0fT$Im)DZW!Eat6@+!a~aA5qP^6aTVZMpFwzX;_4d+JR- zco)_n!3UX5j^OGu1jd#zc)PoHy`M{J@Z;`QYvt-6=&Gi#Z@z@RIZ?;)#b|BWPX*d> zwzs8OjQc^yUC2_1!bUQ?a#`VXzs~xxZ^!N=Gy))$cA@ZW>!)`;P?x?F7d*c1@D&X3 zr&UOTCH?bJ{>7!~8!O!1o2!XEn?_xoIsxlboZovm0T6eNIwZ24)2Wi)7(X9-S(0Xh zkIxWd4R9}LrJGz3S^}me7dqws2gXYWEV3B5-vDpP{De^FsaU(zF=ukuQ^#+v$ z%9)az@rL3&Cv`jz08!}mnFQ);k`D%J8V*RV%K>?T+nz;Q#T5U~v1mCGF z0Clo3dWnwK6;j?CL+dLs)U{UUHV}^b!Gq_T+Qow}Ky18G2tl@U#;&aJwl*4B8b_{l z^jAfP2aG#}227a+xcYgN&G{P;z9;b#(JWmU1H>P!ADqf=j-E7(#07H_H4*nEE2Q34 z?C$AsFg^9d+wzoWNK<3$E}P|K=h|(w!w7CSWW{wcVFpMzYfJ8^*G+sxD>AE2n*G*2 zk6L?ly4ESw{MFdg=srY;sxP~_#Ml+BIGMD!5=c>O6~xo#xMwi(lKPu7)b9am&ENH2 z299?hJb$J-kY($K6n4)4la64#i4w8a<9An8RRO<+P*sysRaKVKR%4u~se=!=(ok1b zl9EzWM=9@CRZ&-w+O3CDSJ}N=T@6$Ns;;WGTSZx2O+{_DB4`IU>{e7#RaOBV_v}Hb zGU`=SU@9sS?Ba+57{(|()`}n}lw9PvWt-KCfc*B0FRVP<2<&|7SJEW6Vd4JgK0eyLARrIz>@*t`*5+w82N?HvgVj@BPv zZJxP%_#XFLtQWk5??Dy89>)cfpphsakLpt%DeZrEJxbganAh<}-`0jHa^~PM=ZlDs zM$@$@cj@iUf28v7m}HS9`mm^oWXssno>%J; zxKA{lHCqom74Tt^!vD23^&0_Ec~G*Z((e_A7N&DZfsMsCK8;u_!aE6(-H&&Q~Hp>{p0zGBV%=!<;%) zOQ#pqG`m!5kynQt#X+b5FUe)dj;oB_g>DwQ3dU7uf<4ho5EwLsr@IK91TeG2i*HGMJI9h9Fx$E11Od=wn) zv6(PYiXh5JNCM}xM8?7x=77hu#?}S|_pnvqDM&|DgpD^ytsvqNCxVzKraiB~ zE2{k%(R34HXpOX15pM7tXE%vb+%R*yf5>FWi9814$h`UWpf`16NuJ>`L)HI=sQwL? z3e0g*LIg0!p*U_zhMUm+B4Wwa8?GQZ7Sl(`Zb@Ao=sk8vfDx4nX6f6dQZzE~$0)W! zW+!8KspGRt2;xYV$G=`wwD82#|WeT=GE0}*In0V(V9Fp~zpFU}zD`{MWf zF=h~?BOi6QL=FnCwMJ0gqg_VcdF2e8z0}SQ#FX}E5(E{RX^eQ?M^^@go1HH8^i%xHWFpH3P5}tw1$72(K+P~(x zZ>oJu!XY{@DmRJ|UB z){@ey0(giFtLa@<%MV4O3*yWb4&?T>$q0%UU`wxuI}*7w$EM6vhu&2bo{bs49#Hx= z8Z`yHnZPeM=`Yq?sdi2->7kgz9}fgf-BLNI=LH>~5-Q?Mdsmq8;m<&^hqoOh{}ZiK zKw@i+`I||o{;+{uQy>1d{!-STW`{=2@E0pz`(8**C~=zwsbk*p{+7xdMyk%?-3X(M z;XvvMWB|9Q1QT0XwFilrU7B0F-%_D-YWD3*0@(HItJE5o>H+^Etpj1((-9J>Hp9lA ztevn`Bav*VJ;)h>%4;58A2~Mp+-9ZU-dEYUIbZ~U!2hdUf6*MdD+p{J<&J-}Gp zmPD%isruX;^_(+N`tMFe&~Gob9{6CV9k4#Y@1TAA02fFm!FL*#C1APKVvjR&RRJV= zTDIBqP3-qtukGDKbJXv9KpHLcdvfj-tlPB~*2F9W^?m5f1LaE^QPN z#iLx~uqh0fA10iFv4lkL*(z*90^7uh9bTh5wB9`%&K+Abee2z5kSqAk?}zH1>7ify z=U=Q@cGq=Q$()X&kbL71oVX%Eqe?jW5#Q?cz>E8RO*WImpQ`JXSHD5PAS2uu z1{q{7PT&>+usnVomz=P$Ycq*gH8PNAi5z6oY2V^PiYQM_Wc3t7z`c;qw8Xfc8HVD{ zT2N%|D~jB-wc}>bMm*lOFwo014ognSm8)h^P8`64g0#q8@-r~_$yhYV^!hpnmWr*7 ztO_bbbBiOv^&c$xUgtBa_M6G(Y(d=Y+ark+U3YfR?D+8YZj`DdBM!c9=iU7_|M?X9uQnt{F-HumIDvzgB z0*i?T)`7025}uqh@uYRM71$caUu{dJ`kG4KcwHol_bqVaf*p;vmJod2D6kC6TS~8Y zN)Jct+-D>=^nm@5tqTT!J&RV`KN^HY5d>43j@`PHbiS<{c3VjcCLB6ZhOtd*N5n*;V#20_k;HaS%^gyTa3lG>{26$@fOsXRq1yf{ zMCH;hW#aU(@7|#BadIBW<$AZ;RNNUPLtPkaL;>VoP)fo<92RBR&kDR~+mN!_J9EnS zd_2ko`d5UihD0ciQLeU)Z1L!;iJvYfFlp)qrtT%WVc(YC7#tY}$~W;XcPhH}jfBmY z9Pj_s3-Mql;lwHD>Gh5(=UWa)dTd0>Tp_YfU&_7|oK2D64OQ}Ag@i-> zP2^+Y!*?i0-5l;ay1V%XwAhR{&H@`_KXQ|iDLqlQyl+cCbC~)WhEt&k@Rr6ML3s3( zwE*;2olX=t2C-2Ranl4~Lf&{KquAp<7&6jOS8L1>QCr`B>s2->D6Lg(d0u#UO)p4W zPG@6TBE_mvtP224Hh@@+Sz&FklORRi_ra%K>%z`;F$!>4Xp$0h;Ai)4SR05(>KnyA z>5XF2KoO;@;nZY=uzbv_`n{Dy8=q7!zPT2bYsYw>lC}r^pR!O;_%6Fp?L+B-d`vil zg)-<|DN6PA6HeMqm0}ijLkj!Xf#UN}F+z?*p5SZz^xQT&VBn)$;f|DqlA5{hJe{-e zzMQF>v=-Cxeaq%4yhBY9X6ua_%@E+bUL~lRiIej{J$CHgnfk8!Y?|Z7?v5YdN#ik5 z9YlotffhG_%n+#}{Eh*%-bDU$lJy`@z*>5wL(6%tLMAVK2_&*Vc+ad1{b+9dI@YZu zyR6T}65HWoZUkb?fC5VCkmMsjI>1N;)uUJ|y4lD;?;huiQ*T68#R!GGjJ*2jo`3JaT zD)mXYP95UL(xS&JDlmxS#hgy9=tsK$Oy{dj66n!izR!yW|hJNlas>Ons<5&{2{kpOTG68gh|RTg9xL zTDbbjy!>M*ad8}+vJ*6oVxMx}%GkzYWp&iV+AcKJKLDrCWa=c%x6h z9`FJ^Y!y}k`)G7xcfmg@K-Sw-vK?_krbO3k^TUg;V?d`?mm({% zg?!g)Tlv&U7Cs2llDmD>-Kau=vRc^b!otRNj$O3YP^uSc9IinnbK-v?$+tt8$+cIX#l$tyO)zZ zBOvI_HcE)@{devLs3YsmN{bV#=GHAi%s)zH4Spn=mLsVaN~I&||PBeFJV8 z$mRNt1bz@=us_5Sjyv!ZVn|bWOc%zD+eFrQ;@}2Gz#r)+7jx5<`#lr5} z-W$otO6WeZm8=S{5F>7;UvizAKX@ykVI-YgPxf4I^XQ}5RcZoPahl9@6-$|dlr;PH zz77o+t%X%tv!(u;z8||AHnukdLKH7zy`U5jb@zJ6zrzUJ8q4C>I2v2K?CjvU?^+>Bk|elZ-vrQ7=c8aI#=j#Xoz zuulQgl&C(t6J-W#f~P`Cp}|Hnv0TGOZ;Xkx7o8<4g%@5Aul$R03hZjy63`)w&k;a} zWI0R=m7FTw8wWKi&P)z`D0o&4_TVQ*s$~V_!+=8zfSUBJK_V$4EQgs$3bvpWJI?q~ zdpA&#Vc$ zjc}K#@sbb@Lj|H>e{kN;{KS37B$i_-oj`S_eK=Yt6482KA)0@3 z{oeLVAd%pHfEbr;Lq|^7X(#GIO$WRXMSQ)}cCR$JV3M?;Obnf*o8y;Vjn4Z+d$Yz# z-G4c%;YWw{_?{r<*8;R?w3)Ocr{=&X*Atk%;cABXZ4@>Ur(UHbrLl(2g(C(e^RUjj z;Em#mG&7efyARMW)#I(IalswY=HEz|6MLU^!w3oThN#*yp`@dIX6ycc?yMU6^c!zf zq0rcGuMWa!-+4GB3ib2At68mEN{&Nr4!>OynFzq2&K+JGvq^vbjiGGRJs`y7Q7gRh%!}1MSyGud_U z;BEj4{2c-;39$8SVnAjXL=h5kDAdb8Y#jil*8sp87=VwEU(c(MUF$vn8P4GiX{6qZy*cEyZALQ@l`ilVG8m~>!(x+_G(!-M-4jsj7Q7Fpz z1HMOnL-ApLzCl4@s_Uq6;LqH_Y!}_!$;8#p3?J<4zIGz`#mf?zy=utSZ88y*UJx%$Pa#yAHpaz zB$Y?BHAkIvcW*OsaW)@!cG=^yZ{N}2@rdB@+~5h{~3P`9S;GDlnk%g(xgUR`or}JM|6^i|80zkkf>tQ;Q zKO32L@L1NbH2=JRe#L(`QvLeX04m7KzQgjrqh%;b0uF?5 z_CRg`8?h~e^@}ZR5G}44VTSxo32eYCeu2*)&R-rhK;^$Is=^kGb|gC&Y`IpIR}gmB zVD?PVU749PVRuz;o$e_*J)9qQ*BY5${yTR}vOuJ^>K+J_nLUH*m0^MH{O{$9R6TG5 z`|cgie^q%63+9ru3$`>}bN-F*7t*l1Ri0<=b{ISZdn7+B2)rh`56pe_IrDB%bR7oL z{W}r;B|cPe-dK~k-m`J|*fh8omQX=BsTiJ)I$5OEuUe*$=cpNUg?uHX6F)uHLCl zuL{F18dwFEH{EwvF9!u&wfwh6fs=~&$uNb>{L+uayiiapisHonec~2LLl}jTe~UvO z_M#BKGM->i$iMzI${3BmN#bI1*r8oqnlfwwFMw z?@YV{P4xE!|E)Qehp~N`@&lP9+Xj;TJ&nO7on6a1BU0v|xa^kRHzIvxbffQXy?vv) zzN0R_;}O0OnteU4#O`g5efs6!;>3}E*Bnr7hz4mhR{yCvY0vf3l&rHzd$Ux2X-?JY z!nvxF|DriXe*E)!{PR5iEH90moa>*QUwy44=+?#g>;K32Pt5@p28`xTB=i5!oOUgF zGf;UNH0+mukI`fVv<~6soBn+Q0H8esX}fMpNDL>u)Egqr6dstmC5$poM{H zN9^r;I*b?kx#j*P{h1oJqx4K)|NPn0DmsJ9c9@dcV-=!9%iy^E`6bm*aCrgQF1*fm zJwF=_l%rq24S**B?a%acUmh!YeXPVUzxvi;joZs~2G)MJi%ZUoU+!#j2|2%_x>^k+ zH1unUa&g(`m;X2T`X9zaf4APZsc`mK37KC}Ree#Mfi(vmAJBkL2!lvLQ1zSZ)O8pL z+P8`K8|B4$=--WU_Obn|tE<8F2I>qpm^k7qQ?%aYqWW!M;>f;<9N)e0X|VQIg9bPe z?GOOpjxZdkY(U7_UtI*SvJFIl2g-tgWusM&^p>QF6|?MURsCpFb_F`Alkj+3cX zd6|>8jb?eSlQ1DC%ccrn=H#R*46qgS!Jiuvh_xhqeOQP9my*d*nNhrsG zo=p|jd)>#$&e8%M9S57aJP@{}+~*DmJLzP&?)+G*mWP8)K;N47T0R`~KhB1sXb^z< zo7?;#2a2TgFLp}$oNn3hp?SN06MT1bf^L)yuk#_NzT@+7k|P2 zM-x{7ACVx`?!c9}pmYYCD!fs)K@vcSfHP{!(|Sz;{ka6R_pr59c@s{Zi;H)(v8ip! zGoF>_I=KgE_P#Kf#g~n_3$#=wz>$>adZpA~-AGeYZMyGOGl0u+3earE1-|X6%(k)B z>cs_8dS9(KzVDu*Wq3u>R-o1E&c*chZ1k>anji`=4L$eVYn$NSg8FMslL3R*a7u!U znoX{OgTg?bu|7amp49KMxwrS3UkGR=AnZrrCJYo3?{!%KryNn`BrpJ94BR*sXFECl zk{-|-bfl@4mkG39{i;~^PyJPW?-;CCfSzjhJ*W9IVPKD6j;nI{7&xBa6;3ubnjk7N zh-$qZnEu@ZELN3g|J?)Z29^`hAdO$`%Ci6A1A|3t&8BQ8^6!f5EO!viFH~Rx5^}Qn zyTZxF7R0ws<5%sk)cNnCYWdiDx8JqyfgppO)PA>X9CHr@(f@AeUkehlQO)^{9yGfg zA%M!pNQo%rN1lv7O=?JoWm zDTlAc&5n207RhnCV=6}QwNr)H=HLu_^Gfpq)QY{4pp7*J08WM|OsuG#3J2uzBr6Dz zAe@N85UB#F(9H9DZ~%|Ulp$j3aU>OIA@iPapzdmW#s)w}R?dIZreR;U`1PyvRD>yl zhE5S8;~^qj9xZmu6^6u1h4XC`e(mSjLICLHk>sd!x=xt9g5qX8fv7}MR@w4<*_nm| zn?d(b42{7I%3CJ&iCFRa5!T5AqBa142hBAY5O@Fr`Q>2!DntH0xsWpWRST@2T&%BP z15bwyh&zq9n`lxf28IS7HMbcX{G40;zPyH9Q&_{TVb-u~lr@bSukxdT7oYonyx=zX zIR8SV%<2UXO(`dL+(~l*uPH523>x$K=Z?9XLsLFMRMR57q-Y%YXvV+8jqaXnf;NT@ zU{=20*E#9`GD~UkORc|3*oE3`h^q1VVGyqdlK)268)w?nGRC6Fq^5KOtf@z%|eTHhwx_A!}|r_Rz`s)8na- zw9lyBsvj1(Q8%t{tNgkqZPJCrguAY)9~atpaJ=pL_Tk5Gsx0EFedq!PqrR~mFgiT- zedlcSyxhV2s@@_t763^t6(4mG?w4$%#4Ef2d3X)b&lbY6pA7fYw{U;_oZI<$^M$Ab z$(p3@uYvCfn}sxFM@3!I`}Zqx6_SRu9IWu^bw*Z)yEgcy?3o* zi3Qi)W3Z6M(1LSb5#nCzSH7wqxPt!)ByWfhfEwDu4PB)9@~M(&&l9nq5ZXGy=((6Y)Dpf;-BBo|-_VctxjZJ?P@PVo#H+W>^@&Q?8(g0)?F-h$>LTCB7&$rjmSfw>S@*H z>YhI~O9Bi0n_t{x2L*5ce%>r`t*kO;lXW=Xs#FboW>~b zQsykaz!Y+ldVm0l`?43HC0-JiB9>;sdMW-*WUtAMHIqCyF)x2yg#}$Fv4bN`8+N^# z5qmJE_4clpr!5?!mm({67#xefZ)91uvD&?+j~=H|_A-_n|K{N%pPDr#MPz}U?RtQGYOT|&;ZE!DmC(*BZdu=KUE=u3w_ zv@v70X#^Q8*tR?CN_JZ+CW}!JA(kT6Lpj;KZ=_dq`?fc)0|sAL`yEUSRk{3JQ6Ng& zVF(;wc>F-GOY_c_ew)jR(MOvHL&TRw8}8M7zdQ2*BdUc?rsr&m^8%!G$M5AfrTV+e zR=8g%=Mz*SCbcz?a(1J+spZ1e71gSVtcOJbc@-SlxJpIxHir|@PRDFdB}zT_lw1w| zR41~>IU96`eljA*Zs4vYcl=%2&&iUM!sqTXd_F|=!Jz2f`vQGBD zzeayPrzSRW_?WL{lG@xgpptI8ci-c+FFDtw;*++E&lmW%im$efS6 zOeL3FcPXYXMsl8GXgm_PeH0IO+>JDQTi4s^pq}eW8F#90tj@ik>ow)n&1b46CRDnZ z)p>bWx&&TGgaSpfY8hro6v;fehr$pK8Xm53vTa&6GH=ge4VdqIp)vNvX!z}p@vcWL zFR$uY?%z3J<)!Bl*d35`{+`Z$>rQtlu>q5tdQ5KU<-Uh!sx{A!8IA@YJv6kun<}E>ClcJ|H{T>*6lMg-k~@| zwL0G=E{ilSRAUp!dg1|BCY@bs^S$z!Q?vygq{djYn@eEe0>4--(WR>>FIb3yf#D&~ z6TFx5YR-+kv$g-;u&p?awGr^i04!@oON(b)4$@+M4Q|TWNS?H8JloyYkoV%`edD{E zAL{;Dd;C%QCJOc(O$klKDqZgk-HODUOJo&)_0oR!P9pqFfVylnR->%2Jp4o)GAqh1 zZAEA8c<0mSZ=BV>(OcK#)4ygrB$6g2`qE_zFDSPW1Ag}B%)RS4Q5fzD4>LM@dSUA1 zqTXdI$%PBEx=}Wcz|Vr4bq3)uxyZH$;ss`wb(|}Qj1|)@dB(IzQ=zTsLK2x>-IUiH zbiv=std^5m>btr&YEnl>+bA*c+U}lHPlqvG56}9jC#-02e=cqf zm)mW3Qz8`kDInT#n_Aw?7ZJAgXZu-&HZ9%9d;KiL{^NV8>(x*; zH!MkS+`5D`$g}<2kJ?7VZul_g996virso&0pU0oQpIcDVJAR_cBRV+rQhl}lc4owu zDVt3m)4?jWTl%iq0n9b%ft`4JKb1?NY7-Gv*iVlptUqqw$3rR{+p`il{peP1z8BD1 z5SzR)xEprA=Ej3lej^rk%)#sO8pj-{Nqwb};NUkQo_&a1f~KR1@H%~do|>1gyhKPl zdN*3g1$Lpz%dIJRN_MnaIZ?X48dW+%4t)(6knpm~;kZN<0c~;r%&PsBA0IB>sT_J= zHv}L1cy_2YCWUyoIXKA)=E1R7**D*?sHCquNaz95m|Nd&VHvVzZ@`z`~?ZMkP(^+1Tmx zM&z$oinoW}yq27Zs~i3}vgiA|l%no{c!lkU#Cz}#A`@*=qa(9-uc|#po-ZvtaBL!w z_Ila}?VJh$JkLDVX(+SFQ^3`4Jh(koqe}9Ch(%5G(`QEvV`4y7^oet}6h}JaAt@b= zac-02e18_9Leo~g!lnCOr$zAIrsfVEjas^|loAbzO;&?PkSo1p-SUdONZ1^CG%WXU zbB^xUe8v0klYLsrJZ?ifuI5vaUxqIR;)o!nUOB>i7k1FRa<~{T``ikD+U2912U?4m z{p=9y)vZ-p+xJI@m-FW9mt>tNT{k+v5LW(Nek(0~pyc#W%Qm%``~>~YDJtiePCN`N ziou^6vYY7c4N8%@x0Chs{`I?O*KT?rxP>n8d0eosP%I{!Dud=SNOhzc98_Egb+q~fk%gnfOkoM8MMdFKcNC}eO1bs#mv5)8cDh?j zign5|5Xd-(Y zYAREk+?#al(2TKW7jdMxu6-A+#*HzPmu$M2OLU9qW(RLCj?IxLpgDM$b@uFETUw=X z^C^Khs&3zXbNJ)ueOiLk&L&;z-`9Gr@gl{@H0Gn*u6=F{aR|CRYU$OZnbR_p3ytYE zS{op&uJ%sw;i$vY1>+HM zL!S)>Uh(J8;~DhJMjYUXz%Q&i7qNzxEtTt ziNMRKHX>!8ntZO`|E@9R>WvHm-A)|&`E|XPKDfK$RjHr-XiM--4)$p~?OXOh(uy9; zl8@UG%P~5gS*UyKdT4ayhPOlN!smY8`LJV}`Lb-_s#<#gRDDQfYCA?$0I|^g3Ey+| zeJOGI$?=1Korx%`+3Od7$9fU6_-?jZ)Z6H4{~3*8BEWUZ#Z%yj8};?mN~pFu$mh z9&YG74T_24esxh(fj$n7(yuJ_>N&7UF3^nL;Vjh6ciG)@uTihtnbDNJ(h_iPOP3goP%2B%&O!3kVzoxgUeX)(ucQre(Z+Yxq=JdRyv^T~lmgBJ)w-&hOAfJ#E+5(R02HeQimiN3d@_ z_-Ass>f;=p$Fck0B28Lk<=UAE#F7jOQ6R0>822D-$}{TO#NDO*_Y>o0I(u&-?NN3P ze;iMi{F8FYHgYR(u9(m4f0f z;r84FRxrygB|$f_$=%JP$yb%nCwj3aB?B?)x%VsbD*VTFM9mU7LM38Q0fyx)mO%or zi46iXc^7)I53xbWgM-ZEnTZR*2xoUtyHUcfbTq81cP*=x{@g%#RA$>jyBNX1vy`5@ z_Zd(rH8n2QXYgz2MSjMzgDSMx4B_Y(oxd}QG~W73_^-{uTO8L8ymo;HAtl%w|bdjYf4Q%CcJC(29D9O9f^YqUD94xK52!hzqH zE~JA0b#$@*)#`vX@4oDs%G&0&Eo+Wz+G~t8`kMRNo;8m(*4nYP$e$jMKkD|xX8`XG z9T}a#ZlFQ0em?_R@Xed|Z&~~hdA5C&8c86t^O|eCyn}n{g1y~S@_@WV|4BDY;8ZS| zr&dv7dIztD=)!>$3lqnypz0H-?2;^VaC;wkWLp3tXfJete+#@Kq9KEr+!OWf%X8o1 zEjuR8=`saGsen|n0)HwP;ZGg(5g7Qu{QR1?CZAe7CKi2#Lfb2R#=J*C6> zxb>cMwma+(UCPJcgAAak`m{^fTN9^@jNh09Lue#J6 zGVZfh)Z@Nh^+)9b0%}oZTSk>4*W<$H(*b7mT^s-UV|-#LS?g8i<1T|V#uyXb19Q9b zO8#hhyIk54)z5ZqH&;!ryvt6kIv1gra->bPb7yTwyzudZ!>2SfS1N_?A{2vGJ?$cJ zz+ZKrqO~5dfUD6H21aL5hn7I`DdH?faxHmKw<$@+g@}jEb#-3B%xY+Eu3i8uN!P6@ zz(3EGu35`>OJP+bOk+OX#ffSnxFskG)DqSl#L^H*!03m`9uSlU%`(VTkb%>q;~!cT zPOG1;6g5Lq`+dD)O5%C-P((-QCC7a;cLuJtJ!$6f?aU>Lxjq9pECT@v&8BV@jams8 z!6iu+d%G1cwF%LiG&5IDj;3E(EkzD{W3>{|KKM2TjQWVl;+5+(*-syNpY%>&kNo;> z>Sml>#h1RFW6_W8&d+>}!yjlau6>`PmTKUA;N9{1A?ahg_MeytZT+LH{+ctnca&bF z?D7B|QC56we(Z=f5I^`?>c&scJzm;8sAte)fk|^Zv0C(z*ZhHn`dBQ(e)^q9()O>B zF<}vi&6|e|4kq=@e2BxbfY-=8Ov!`EGf+u4C}7KLQ>ST;B%R+I69K z>)>G|gfv%6f>vx62UwbZcdcuSdyHTCjPndWZURtPFqzv-o=p7$8vkPzxwS!@i$n|6 zXk6JKH@9XKbK+A_g|4s|t*o6k!c!v9m>Mt@9h5wmqtp<><{7~-@JQWsUX)s zsUO0Y?#Sw5J7UB=EE3z(5I#!6Fb0(-VE62 z8FFMB>KqE<78yM;u-)i#*G#6vIv>f$6co2Eb~7iNDM5Di0jxds^%RJ74ZnQSRa{-g zE8eBa%lmd?2^?O%L~D69hvufa(J;0fM*wI^gEG|R=GBXv5L1M#Wc>h4hXog=ttZw+ z%|p|%B^!HD5K7DApzpy5#xuuWs2tQl)(XN9hOmh+ZA`{RC8XSi6HyaS=gyp2l!7q^>pTH+KSgOWxcAa z`Ch#Ost!>qQH&c5;d5w090?8r3uh#`nUe6&DM(RaQ&NV)a|Av4A*L9i%>o$Qmw=E~ zh@TyufEFb(rYyvQQrfj@*^apgOgA_2#QrabZc1HwLVNr1RCm8^Q>d?`j-gQ@EE7iHT8{WG@y5?J2X`2Y>xVNf>&*ndA^?Aq25>ZeE` z)+sz-TaK4yiYKs;2F_Nmd55W101Mz|`r+JAO7MY9bOMLw9Q7di)9BQY$QfkK4>Q9j zrW=&L*;qmk%~?PGs(sz?kJo8w zY6N>sPC7|-IkAvE8HiGrjzIMS0pY4;FJ7mYnml{(OJ+O*MMZdEL@-kTUmp>Na3Y)X z?3qasIJmx`2#;tdswL6#$PLPLc5t0SVy0s+A2LbEGJ|3X3w?+8rR|E%vAivFAmL-Dv4X!nCsV?9>6w-0U^ zgLE9XToe~jnn~UXm~D1>3a)Zp%s;{<6#VXQ-I8rFrEm7)NB>a&>HKe2$g?kfquy`x z4A`%=D|7l-1kcP-UgT?-+yj|`H+QbQUs3(zgB%P6EwP$Jshrk7gx13dG#c9?UT{A> z6Wan=cZIL_NfxUx+oS%KYl>>un_aN;JQ!hE)&)9u*v;vnt0&c79Z}$~tI+KztSe!v zX};h!brIK$)Uelbkq~cc)YAy=rZAAS1xg0g2&s^xM&9yt%f;v}4?`6O4x55q7>!H`kmgB{nc}?N-&b z+)Ue#)jNN_34Kl0P7}iP3Gl4UqS&zxm9C3E*6EkT0b(>MYno}=6qKxC11!Eo&07n z`=aUWgPcV}V@VZ4%djYTK6?Q!4g|=0`LmL$n^>&8#3q-iM0a<0*#OxJzRO86O1dOZ z-`UxXkfw$#;m9Xj)W|k#YDh?%j#GLXk&K0)$rLP??1=8T^8JUw?l1GmpUHS@2!M{Nn)9_{8U@fq zpo*EX4}~&lL@EzlEx2@_0Wn7*UAn0~Wvt?7KtFr`{Q7 zDH^vkI^0oi%`(b>E7+Fu@!M#Vbl=hxzGmWt!U>B>NxIqT3^b_*p;zP_ImZj0LlZCU zkFst_JAO9y;?ApSfiJIZ4A*6aJ@+suqtb~q9IrWXgT&1_lW#U#1_n76jgVHGn}-71 z0j6j9lPkJRfYNr$&|HkbesKCM>gmGbkrhJ)&*=}Z78WCog{jh|t_)by{l@yb049s0 zurZOI=CX4?TOIvJB#+=UP$#D(0!<8QTuyqot{kYuV_VChA}nZv*c zMUsBF$PqF^!H3{4&z+F>A(suSh@lWao}Zq6Jv#TOB?==!Z7$I&(RL*#@ZrR4QD1Sn zf$2(&Rzi?R@!?A1yR+qkMrqH3Ma{WTs4xe?6(r+q?_g@eja=VcLq=fZDzOAAMoQES zU7W#^?$W2>4A^)iLdl1WF#2#SZ#iITvw?G7uzWycq{G8=QMU) z1}}Va=r&E~r0yq`{Si~xse>|F3c!x)(m?@r+%+p3L(HSPTIDsx;mBRPj(%XWuQl$YQ^HL7w64e zHA+I+q$K?Sg8*w7tCC6p9VKBWI!8#DiX(f`O7hx?8F5rGA_v>04Z+BDT9JjhBY-;0 zdh1l-LM~{BDRek)J-AJN#uDlw)dRPVN(+6PDaIqH| zl@wDy-t!DJw|4l_#nzeE{*4RED7$VJiIgZkX^QKbxwL0#=hstaik4qq1nxX;a)X>V ztWsB6ENUj|uwU1tG=58+4yQD<)b-Wl!c7O?%p;q(efWM(^iA~X&kuU*rAM2tY)^5XyP5_%FKmjoF;!f5!s_zK4$YaFT(D@} z0O#6izqrz8%pPht)GXLpi(ZpwgJZ_|;YKqN3?rU9M_ySnv6%-Kqe8U2tP*EY=@}kk zZ*eHm(p>rH6rcwg4@z+wCNWK)-ayUtBPXEf>oEH#qol4uhkj4QY8SSH+lHKjBi`G&FIbS^CMh_eVdDoO*iu+&0giURYi`Lls}A zNz_M+F>vs=H-y*DbRS!mM>yj2k#yM8Fc%Y6naG5vhrSvX4oztO{O*v6$+zgc{JCpn zmDx=Ywvb|ei(UOH_`1d)()xL-RSM^A|);-u4QL7*4S7FGaGl5>rx|Xk5rmLv;+uyI-ufs zhZ61_s?!)x<*QZS7pw61XUUSidbd7dJagmbp%b8%8n;zw-5dq|=36;%$L>y4M*OfC z5|s?&aODvO=uXDWivA@R9L7lpn+5FaI`V$IOW)yd9gG1naC-rPM|pfi=~JL3++t}Q zEr|maP7|}FMBt`}8K)PQRyv_rdO`v%L60b4?PXOOFX^5W6SiWLFfMI7A6_^rk1@p| z)O9jF{-}kvr!kZLnDMV%q|`-;PE3TRI7>;spU<2^QBfT*Mln@Pn1fddSv@%GH*{?7 z^W5a_AwdwS)Q^|Bv-F<1VD)kQNeZ-B6B?JN5_P_FFdY5mwuwsKAsDR~FUdwIdp&H; zj;|enUp^Bi_V+=^>3qj7KIfbWvXr8s}+BxwaXy4W!o-MxH$wN5-=lSX&$ zY$)jr)zMFCdsF*9YVT+D8Ak%sC{eSF@|=RGr(qK>G%Fi-_8Q;$c(?J=&jYvC#!osY8&q$tBq0kU}$vi$Ff@Uj;Wg_i@(l<#T zD_s1lY|Y|QyaqTzBpy>_vWT*nGa@pgZHiW|qgm4Qj0^9?)SykB*$R<&hgPPRUp~DY zE5+sEW!XZs);(v_doH=ROuv14;e9;@rPLhsK!x6`!h;tJNlBt5@JIl$x~dsmy>Dt< zQbIs8pf_jIpaP8qe)XOeyH|Mggar_fW&&H`unzDH0dTa~?^Wx!ut_%$p&?m-=bZd^ zH&tCn_P&2)R;2;vx!8ku2MSu&HA5x+Py}e13TcB-plqXy77dUjNFb%AN}BELAabLP z&2`i#7zuf}n|q22#>LED z%PyWGnW=jFls033ymP{0h7~@-b1I0WU(JmehBPItkB@bX~ za42P1rj->5sf2$#n6o=Qw(*8BKE4D6g-y~A+mno3wqAxSHYlBh`I4F2ejl=(=#2YDaxB)&3W$`p1p^;1=W)kICt4YDD`GS?yn{$5N4K$<-GHKVB`|8Q6a8WIA|OzoT{dnJ}cs zT^&5M5p=Y7ZVd4EE{&*ksfVc}u}TOfu~IQ*rc#CUZV601MKr!rBY|LHO7`MrlHD3Q z@hYdgXB28mRFItrjsgTBJzbe9uOwUKn3aGv6iEKKVd)vhjQ7H+R3MmC^h17fJl^?^ z6;4zDYlb91iiMzY=)DYap<)d$w7!6@q!pi+MWYpoV&MmppHp3#dpF`!lNb2cdukN_tiLQ|s0w(@L^M8m=x5n409IEMk8C*y)@)}Xm;oX zDjDX86L6Rw+#tlu_Rtqql)TmpH+1A;yuZ{QOM5bNrEg6w&+yC2_7!5=yzi%&HxCX) z+%9kV;o#z&Dx#BK>=E*&z=XeA=9zKz~tU?^lu zV}$b^T{K@~#qBg|dr%%5H{xAuCRr?VbFFUK`bHglXZw^(szMnq3Dz!F#YVvSf z+AE#aw-=A_QXK^kf*u6GJq;e+cL(x;n4GTQTHzwm_M_&TCtoduzk2cE1Gs}XZhdeX z{DL_u8ppb#KUu?ed5fsvOH4L+arV`?_vDtl$?3t>@&ep-g`oyWb6j^_JTD%h!pA#~ zp-PL$PB=5JDG%C{K?rAh5Yi?ITJ~HX+mQpxD#z|Mng3o@B#n*_!In`&Pt1tENe8)) zkP+8_>B~wQ^0-;04QPlHb4rh+(OD^9;eDn`MeWh&XuC+yU5-WV==w4;BRc*|vqvUX z%vXxg?&^mzFO~*yL?<8)C?#WP7zqj5Bm_c3K@Diw2T-Y7d1MpyA-;pc$_Hft~ zCMpEwIKIvxi*-`d#L$u)_OkDe{m}<1`}ap3rhFC}ETavHn|5 zi3o~;9f(86ktJ#42jhzkbu;fGpoO?U?M3lGOmfF~HUI@J&30{KGVuBCnXo1Bqk0%< z_>G?|EDoa5w=h#HF${C(G1-8gW`CC&H{D=wCdAF1r%;*PL~ykq1A@lx@hi*AS#HdX zI9@Uw((N;Jbc3h4FslfWOUK^sPZNa{ixSD%JgbomYFZ);ZHN>y4^o$$%q8(2LQt9y zimCMWENUm*NX^H)l-f-Emi%=`!BJg<*>p_2y0PR6P~GdB5=W_lr@V z(!vU(eAVG%3^anqoA$#MRR$AeETPY+oDOb3y@LyBCN@&$}1meY%n}Oa?S8v6JKwoICY(B^FJi zGacC_`h~TL*U$7>7(aFTV^ASA?PzGjjCxPB(=TL@~D;z1K;R=)!in zj%9V>Wu;7U`a~a|eub}mcenr$A!fYAlaL7-^7J6fyK6_#lJZn$5|K;hf23MgzBkKd z&E3>z5+9IB!nI$!fiHn1Lvc>GP6`$tPVPGwR78-TfHopTvApl)CZz( zGmQ>G+M#hsv@+6%6xW{1yG0<q0uYd8F(Ptp(e>vtI>G(~@TBX-C(iO>y|A#$@gmBP8A-F_ak z-xDqwUyl-j&;b@kx4{=tSElf$Ma6ZY!%mAK{9Xq}FTwzxlLhwB6`)9cVNeAU0}S7+ zK5~JC?$DckF~0~Jo@2(XsPsP2^n>3sw=zq;QY-6A0wnmdiSD`@!FIYenl*eL3lH8V z!_+&AV_~on3YvwJMiJvE2!yCTk5__E3`$`0EX?uXc68d$ZUFkiiq0*LA7?>7KyN^q8=)ETk)m61y6vkiZo!#Ty{GTpn>D zqg@O}zyu{ta_V^faPMbmRVa~878p|Trm&=9bMWpevG>QBV_$HyJIh`79RJjovgvcm z&(!qm@y+q2R-6n$UFk*u0_*%M>23(Fj|W zMxQz=Cg|Y)gQFImLm!54$IgxxAjxO%H$5`*yIV2m(c+=H-N6#Bf$ivB8f!Y0d-Trn z^D2)#uTOuwo_yEu&Yo2Mi{Jpl*-qfeijk8j74VZgVW|c|0B?0g7cOu6x&j_@$F$r9 zdA$;8+~?WT;^q3JNW4yPK~jeHh6 zxf66RyshxOl41QgrGB`VSR93iE1vkIFld|lav0LDwa^~w_rOX!6Fu>@Nacrg6B5#i zNm{YJI3JUssbP&b@8C-UlFdA?I7dk~?Kaxt1^7Q98dU}A7Gd?hh5yylqliMhO+v2m#I9%s$BT=ToUB61}^aCZBplxMk_~wgYNEb`p!r0Hvh%wGx}zrwV8b zQbK2OJm@*3V#X*8$WkGD}wIl z4M(7r^+OjF&&BjyjXL>h=#I<7=A(oAews)f2Kaomk|W#KKrOyJ_|e*D0(v9b91hJu zh`#g#mVztVZl&n=CS7!RuNK^RIO8kpyvwq07;t<-?l2NG?niH8`hFRA%g4?tGNyT9 z1t-&g?V!@r`}+FU`5)7Q$1zOl0y0iFccWW=o*uly4RN0)R?4@i%|M6|UD2{$`D8Kq z5^9&g;Cl1$?WVYgBaQ?y1TmLK z8$-i6@l+IoAh=Zw(<+)fv9j+IRG6SI#^A;motF51TNKJ;BWj14<~7t@S_q<;L~IBl zCw-j0H$MAg_t61*NAU|h8nO{3od_kO``e!G9h7#{=aQ$XI`YqMM8y;FK_$KS5346i z9nXA~A7!$7H9UG34JIa!NO>J@a@}Gz@S67Ym-(P03R$V8Oc+Q1&D-5FMS$5D*W$S8 z=<(wzuZa88eW7LT{fXqX^reC@#lO;iy0oeFpF8RLMGu}di6GJ}Z+^Ze^GM}~vAy1> zuQ#Qsu(Dw>iPj}^%|r5=dme=Ft=h{x+IH(^#X>wMrz#|wKxD$rU^NJw{iWJIGnvj0 zzB@4S474jM`;#tp%^U$)KF}u)-6+tC!>BJHdbElzqAyIg%h2bQuU$;tPDmj!w?R@M%S^mf+wbD$4lMHp6U$askgCJTlI~N-{-upJ=U)wZ3uuWvR=B z#n<5J8xC&rTC3&m3TnrIN}Z^@4SnY5r-MsFYx$>^m%dyu^sLU=u$dL)ZN*mTKw(Sp zg7?#b-;Q{`U+H<@pirM#l0}0qAR>y;!i0yXTK;ScopvOuVI(Ja$aA%vH$;UaFVCl( zyxZz|aqwHmMRLjKWzY|{dk@;9#a{Gu`dfR_@h|Ji+5 z4Z`M}R(3(CCOEp7k5rqI1aOR{G{J`cZ25{M?#$r#b{;e#7R!$Horb z2VtY{zkC|cC3^RL-uSA*sZHumvXdZ$pKH MFA$tQ$cF&`3%v!xssI20 literal 0 HcmV?d00001 diff --git a/modular_doppler/wargaming/sound/missile_launch.ogg b/modular_doppler/wargaming/sound/missile_launch.ogg new file mode 100644 index 0000000000000000000000000000000000000000..e67fb556078fc1c0bc6d42ff96ecd830384ea6d7 GIT binary patch literal 27049 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 literal 0 HcmV?d00001 diff --git a/modular_doppler/wargaming/sound/ship_explode.ogg b/modular_doppler/wargaming/sound/ship_explode.ogg new file mode 100644 index 0000000000000000000000000000000000000000..9cf5d7df788708537ceb5c05b08db830561ded37 GIT binary patch literal 33909 zcmeFYc|6qJ|3CbmF&O*I*v5p$7_w#v$vQJ;hAdr1NGh^sEhR$8G8khC$yUjpH59tK zl6|R=ouVs3DJn@?2={x`=kvQh-~0Etf8WRB{{D48?&Hik=Y8I1d7syLz0P^Pp6B@E z)TzS&6!`62bFQ3bCr=GqaX_RYL1)gncm=RCpp+^A*ajW&1+o+W3HgIv^WTnL69PV6 z89Oq7a%PwOr_<;7)sz-k!rsf@bMGJLPGNn$T3f0=c&cYb$=kDg^jP>_*4skyR;r!KsBH7pu z0>A+v5@&(#j2uk01pov97Z#a=Kk z0ITUH3IH%5QW2heZic-0@SC*68zdd0)Wc52CJ-_tVhyfNIxNXtc|l$@SdfMb9OVS5 z0}$ad3=wNnnb>~?iY3`DEXC4nmnVw1Bm?@?d$2Krx^IiGSdn=Yi>=I*%8N6s%~Z!7 z0zN+E{ySiq^c~AUkika&73_=y3w{fj>9&)}ZuwVxzlMbcB0+)x2h&7)>m>V!B>Pt+ z2as}fl-!Vp>WH=0;e7_SHqc_ z%>=U+f>Ta~r0;~ex4d#o|GS>(`A7*Mg0$>8EBVWtoWpbPQ1}r5&MDCv-*ZmIBtWw# zK;Ps%rRTiG;|!a}4afQZH3{IfgN`VyLOrbCb47nZSZiQJvtQVfu4y^)-|&Y;qIUQN z9Ei*fk4*1K_`X!<9kN7%&7uq7#{9o}0t+w;=CQehxvTvL1an{Kmy28*X-lxVw % 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%0Dpsa$7HLsN_Is)8kx=psR0B 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

>>}Qw4`vY}qY~ov>HA zXR(i0Lz;g+Rew9)B(dUu_ySuEnG1uF=Lv4F6ly!0q5r4ZgPC)oshk*0Ng1@_Tx zujh1=Gm`Q@9#DucmF&i7VKtR`d0uf1V{cHXT%Ot41{m7`wP7V&n($zO^yc{EhAHv> zSFD^p36}*4`WiCDp*@se43@>NNsd)#9j;~vCJ8;9nwI)YNaSgQyZ8;Pi8$cW39QIQ3j9w&?jp1q{sM$2tIv}0H(zJw6_bx2?f5J zE?;j3N_pGw^yMrC2ZtrPz-*_W>$f4VJtlzvto85r90pd$JK52FF$JKmIuwc9xJXtB zUYWG}+5#fuc*s5Gq~(n;-Ns6VxiOZuk&PcZMAr~`{WRE93zxC>7-v!I6!LZod6X24 z1FvAJno1;TyX{2~X`xFfZjK_>Vjx}L%hMbYYqt^Ryfp&&us<&uMzABf!h3F-b3e5H z{hot(C}kFxre=!eouHW`HP^)m618rY`dds_ZH(868Ud#>ryt5Ps%!tykJk}L4GdFSwWxhkyrsZRLv$T+h0IH-ld+J7NZc*KEc@@cw<5u-O-^yirv z!Hmw{PXNU5;w5=P~d`!kn9bQu@w4Cr!PrvR@3vJ~UPW*$qU&Q*U| z&@E-!2%e3pK5rUoMF&nVRpS_bP>#5*1nk%Q3lpmyc1hcO`@FkaDxiG8za0n5xoIQ# zFeYH!1Xx}>$HGKS*wG$IXf+W9(&F>6vHCUAxaWoASNE?(h;L53mV@0$I9NlYfT!%C zyZalLoT9U`l6{^a^5ecB67#c~O!3_(pZb351-Tj*r4VZw>x?D!p;rCgk>9RDGZlNxjW3aN+lmpFFnw!at-+Y7!MO)&wdJYkcSk1Xf-dOhMm_)aScBZ&JC29d zyu5pJdct_{qu}@*e~u282O>a1u%KN&!XZ@}Q#`LQ=uknK~c%S@d_1^P%&)KI2PUmxp8o8(;RL zj!3aqExp2-&;vY;recEI-9OC5WfH1AgZt{g$`cXSywJV-ve^zyt`1c-%(>HxPF80L zkAHx{l;Y&2D?JP-81%y8h}XfvHiRvZR<9&NrF7T!oyYujg{&hY(`I2Aa z1;A6%$rw<&6i5TF`Y#MfOC5nH=r4ukdWWi;6MKy(@PY*OKXn*K0c!?c(D@F~_hv-o zw3xWcDk~|dC?8W(lTlU@la*JLmlIc15R;dcmy#6~krfe@6BU(_6_$|`k(Cpckrxq> z1us!K8CmGBAB(FN-vR6BB*AP}XQaSc%g%6ka8tBGK zk6OJ2Umh1pI?*4DCmjSt;y84A&7bt(@KRiW_F%fg%I`+d>WYV$VTLTa(@{kaser8O zC;s!&9@^Uj3DL2EcLuL%d@ofZA5=+7~igt-vkEf+>G;Y$EF$PJBoK3*Q?O^v?-;-g;W z*|yuGZ}wlGW?}*E7GW5mQbajGj$?y8auGbHv*huklsRa3y;1V56NoO<3o>OS$ZMo) zC>UkW**`<0=ZMYU?rVSDAnF`@)lhhX9689gUv_=a2VRux9@Trpw&Zt1h80_TQ}i%e zF{v4X(`W!(YF!{TZ8k=n%Z&_@J+9Xu&QJJppAw82vNP61iHo$OEFlZX9WiQ9vFGUt z>=!n*gT0I4VQTn9u(A8ypli&$C@ASzb?L7|^q)Z@_}m(Pn31v8sUm&re%#h#?%JK% zNUf<bN zCay7**rbjL%kzVYdEx-kkYUx+42TAqzlnyX1``tm*N>p3wM0NOSeY8Yj<`RxVQuLc z9n<1rv*t2xNf;jr-s5|@&l0s`x`(YGw^`!iF=19*H|%*1G2ecI=)BbiP$b%x+B*pC zT;kx-w0`q1T<7xESfu}cg(s%JvT>(Ao8khBsBCEEeDDsYFM5E=TQ`({C)t<34c^fsUB-}Z@k6+ZL|o}PQKJSp%AE`;?8okza#Q}(PzqtT!7$$oV_}7i)?BRa6}*p^7lOCSz}|E9Rd|$+R3q4k z#@cCaB;)X}-c17v1Jh6cPsoqWg6bhQI(TrWQb^ z6Skn;xNNcsc>~XvlbeE~y``foL}CS13Z?X1VE1<5%4Zi&_Y2zUG~GmAsf@}f>Ue&?VrzO{sq-$(9+KjjfMHJ+qaHO)I%>N+D>G z`;muEQ{#}exHp9rqb%B%cE^V#qc-*Beu`h^DQWEDo%l+4Vm3}_s4Z2~2Nh+-a#BR& z5G(kOu$Ia9FQsGCAp!LgbOj#jk`EXYy2qj#hd^{Mg$0UsqgieDcd_fEmarQ8YD%6r zpQUdeH3}YRu{Q4*^ii_90ukp$wg!?Vw$JUPGqSZoI@|E2Nl?}2;1)+qN&mS;>ZdAo zMP^d%JmEU-DQmF*;u0%U#KDr}0!tDK<(e+9O1WmLg1(Mwe6Dg6bti@eo_&s74Du5z zSU1-@6NvX6Jx0>$ZK?tw&z$Zl3>^7ZYRl#|a}ONtvTR?a!F$j6v+qOrx;bX$)u-vD zho~=J&p?hE4Fg{n!R=&S#Q6;b0s%gjcFzXN`>mt0!64tLD}L})TGFjt37m9<n*7fPn|5Y$BIpC4nyREjjc(P{JZJ~mx}!!x)-33>ajaXb zs=~%zy^`Gy*Tfdra+x0^B6AQNG^b0+D0qvKE4x+n+Ec^8x~cb62y>;*_zVXJJ@u}K z4klas{b1rxO(?t(eLVAl{(rk?IJcwSod+-HfuRgRk$5@)_Hb}eirGautgg-qjxd1v z4yd469Ihmicq_+CqfBJ@HmbZY72+9gB-{-Xw|%PJVbsJfb&z7&MSp&8hp^5#E+{lF zaOUF|;*5C%Sn<_ww@RpPB(?GlVEFtrn2`xQJpTD~= z4V|x-5OP0QUM?=J;M)VGvR53dcR>^aT`Qd^oh$VR$1=xNK?Lrl?KcMmE~~UYhl_{X zIWCF~W)^1@y1oE(Y;0*tA+6U5)_6Lfnk`{Os^+L+zyK9G&2{ujL2;<(FnPMF`4Oh| zlg2my_h{bz;R^ILWl>BJOu_xRi$F{FC=_Smj;d;g2)i|bF#D;!iEYoxrL(VQJhyyr zDldosZ!7%&oBrv^?o@g+&t|w=`?nil`SjE-PaQu=8LsZu5|Y=^u8UET?Ik{=yF+Xa zYVq*IT+1dy3f(aI4SKqD=)))_jaVKs3VV^65BetvO(po<1ocUnq? z!=9Q=%`H*)o`R_?APk8vu^U^+lkJmHIG5PHEx4cC^JfbYx>q)QIti}`;ZBi%H&Te*x0Yb!)i@rtQzi5bF zsu?<(s2+0I*WjjbS$Xw%-Q=-Qz6;kc3(kD>QD{(23Aj`Xx}RnZLw6b%(wf|>zPAO6 z)te8BP5>Va^0#kza+FXaDv;+AQu8FHA@QX`j@RM`Gc&Y{QfKL?!}039#`2P0` zFwVsbOFTt|F1Xnbj&?2E`ha@4Ly(B{-4umghyg)-D7BFaqgPxa`k@yNzCa#S#UFGX zIvsKs=5f`MM!`L6BCYk$4E=d3i4!6Hjj`O!0~`<0yAjsp{5Z$rneB7VMBC@Lv-z*3 zTL=t)k3K}5Ce4!^*XyYiyR|s9W>OfswM)B*@gLnS<86=FuV>SQ zn28429HJD~(e6l#uz@lz*-dPI4zekn0M4S#$a}3Gg@<;WSMLsXoF4kW7yTo?bM}AY zO)X7%+?Ck~tKaWA6?|EA2y+{y!=Y>?EFF772|K=@$p$^-TN_bF5tN(}7T}yd)jZp= z82aAUVe`~fJ8Cv}lcZ3>Y~ISwZ+}Qx1`b$7KA;ZSg&mBru zJfa}%zqoO$Fn8VJSkF&qOx@k>=j`=b;$kz7#3_eE*>?j9m)g#DVfr+1>GQL_8MwOK?d3u9ZIY?fg zo=duA;JsajEkXd<7(avzp8I0FOWvj3!SjJwBH#*_b)I+W+q@aI2dK*+tIMzp{(}vD4bQSyBnPXN*iF%G;%H(d!!snGLbXw3)9^^Amh$k3Yx#@%Rs zhtr-o&pa?;5fkfvRs3H6RrY0ppwi%YXv@{w+isv+O3yqh>(jBFTk&bQ3c0vZHu`>G z)Y)G+V|TaKnG&R5LTnO+k)Qfc#(n^dn>qZS>?;uc=H1cB?1?Xn8=wm4f6GJuNAAV1 zZbHSd+g2R!Vh{zOhczsWBgtMmf$?#0ChAIi@^Z$$)5*?FTg>OwW@jJD16qP==wGx1 Os0e@&J)6_uu>S|m^^jKp literal 0 HcmV?d00001 diff --git a/modular_doppler/wargaming/sound/ship_hit.ogg b/modular_doppler/wargaming/sound/ship_hit.ogg new file mode 100644 index 0000000000000000000000000000000000000000..a355a1e77894beea7e8c3ffe87040160dd5b9d75 GIT binary patch literal 6355 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 literal 0 HcmV?d00001 diff --git a/modular_doppler/wargaming/sound/swarm_launch.ogg b/modular_doppler/wargaming/sound/swarm_launch.ogg new file mode 100644 index 0000000000000000000000000000000000000000..ebfb4e87169df1c36799127c7ae49883351d42ba GIT binary patch literal 43142 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 literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index 68eaf04cf058e2..ebcd58259bceab 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -7956,12 +7956,32 @@ #include "modular_doppler\verbs\code\subtle.dm" #include "modular_doppler\wargaming\code\base_station.dm" #include "modular_doppler\wargaming\code\game_kit.dm" -#include "modular_doppler\wargaming\code\holograms.dm" #include "modular_doppler\wargaming\code\projectors.dm" #include "modular_doppler\wargaming\code\team_datums.dm" -#include "modular_doppler\wargaming\code\unit_conditions.dm" -#include "modular_doppler\wargaming\code\unit_stats.dm" -#include "modular_doppler\wargaming\code\unit_weapons.dm" +#include "modular_doppler\wargaming\code\conditions\_unit_conditions.dm" +#include "modular_doppler\wargaming\code\holograms\_holograms.dm" +#include "modular_doppler\wargaming\code\holograms\asteroid.dm" +#include "modular_doppler\wargaming\code\holograms\corvette.dm" +#include "modular_doppler\wargaming\code\holograms\cruiser.dm" +#include "modular_doppler\wargaming\code\holograms\destroyer.dm" +#include "modular_doppler\wargaming\code\holograms\missile.dm" +#include "modular_doppler\wargaming\code\holograms\static.dm" +#include "modular_doppler\wargaming\code\holograms\strike.dm" +#include "modular_doppler\wargaming\code\unit_stats\_unit_stats.dm" +#include "modular_doppler\wargaming\code\unit_stats\asteroid.dm" +#include "modular_doppler\wargaming\code\unit_stats\corvette.dm" +#include "modular_doppler\wargaming\code\unit_stats\cruiser.dm" +#include "modular_doppler\wargaming\code\unit_stats\destroyer.dm" +#include "modular_doppler\wargaming\code\unit_stats\missiles.dm" +#include "modular_doppler\wargaming\code\unit_stats\static.dm" +#include "modular_doppler\wargaming\code\unit_stats\strike.dm" +#include "modular_doppler\wargaming\code\unit_weapons\_unit_weapons.dm" +#include "modular_doppler\wargaming\code\unit_weapons\beams.dm" +#include "modular_doppler\wargaming\code\unit_weapons\big_guns.dm" +#include "modular_doppler\wargaming\code\unit_weapons\bombs.dm" +#include "modular_doppler\wargaming\code\unit_weapons\missiles.dm" +#include "modular_doppler\wargaming\code\unit_weapons\ramming.dm" +#include "modular_doppler\wargaming\code\unit_weapons\small_guns.dm" #include "modular_doppler\z_lore_rename\pa_4ca.dm" #include "modular_doppler\z_lore_rename\syndicate_removal.dm" // END_INCLUDE From 1052bfe30a3b9b014f2111e12ba379fc49d68db3 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Fri, 22 May 2026 23:49:36 -0400 Subject: [PATCH 06/36] holodeck --- .../holodeck_doppler_wargame_board.dmm | 502 ++++++++++++++++++ modular_doppler/wargaming/code/holodeck.dm | 8 + modular_doppler/wargaming/icons/turf.dmi | Bin 0 -> 389 bytes tgstation.dme | 1 + 4 files changed, 511 insertions(+) create mode 100644 _maps/templates/holodeck_doppler_wargame_board.dmm create mode 100644 modular_doppler/wargaming/code/holodeck.dm create mode 100644 modular_doppler/wargaming/icons/turf.dmi diff --git a/_maps/templates/holodeck_doppler_wargame_board.dmm b/_maps/templates/holodeck_doppler_wargame_board.dmm new file mode 100644 index 00000000000000..d5fe3fa7b30969 --- /dev/null +++ b/_maps/templates/holodeck_doppler_wargame_board.dmm @@ -0,0 +1,502 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/obj/effect/turf_decal/arrows/white{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/white/line{ + dir = 8 + }, +/obj/effect/turf_decal/stripes/white/line{ + dir = 4 + }, +/turf/open/floor/holofloor/plating, +/area/template_noop) +"c" = ( +/obj/structure/railing{ + dir = 8 + }, +/turf/open/floor/holofloor/stairs, +/area/template_noop) +"d" = ( +/obj/effect/turf_decal/tile/neutral/half, +/obj/structure/chair/sofa/bench/right, +/turf/open/floor/holofloor{ + base_icon_state = "smooth_half"; + icon_state = "smooth_half"; + dir = 4 + }, +/area/template_noop) +"e" = ( +/obj/effect/turf_decal/tile/neutral/half/contrasted{ + dir = 1 + }, +/obj/structure/railing, +/obj/effect/turf_decal/trimline/yellow/warning, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) +"f" = ( +/obj/effect/turf_decal/tile/neutral{ + dir = 4 + }, +/obj/structure/railing, +/obj/effect/turf_decal/trimline/yellow/warning, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) +"g" = ( +/obj/effect/turf_decal/tile/neutral/half{ + dir = 1 + }, +/obj/structure/table, +/obj/item/wargame_base_station{ + pixel_y = 7 + }, +/turf/open/floor/holofloor{ + base_icon_state = "smooth_half"; + icon_state = "smooth_half"; + dir = 4 + }, +/area/template_noop) +"h" = ( +/obj/effect/turf_decal/tile/neutral/half{ + dir = 1 + }, +/obj/structure/table, +/turf/open/floor/holofloor{ + base_icon_state = "smooth_half"; + icon_state = "smooth_half"; + dir = 4 + }, +/area/template_noop) +"k" = ( +/obj/effect/turf_decal/tile/neutral/half/contrasted, +/obj/structure/reagent_dispensers/water_cooler/punch_cooler, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) +"l" = ( +/obj/structure/railing{ + dir = 4 + }, +/turf/open/floor/holofloor/stairs{ + dir = 1 + }, +/area/template_noop) +"m" = ( +/obj/effect/turf_decal/trimline/yellow/corner, +/turf/open/floor/holofloor{ + icon_state = "smooth_large"; + base_icon_state = "smooth_large" + }, +/area/template_noop) +"n" = ( +/obj/effect/turf_decal/tile/neutral{ + dir = 8 + }, +/obj/structure/chair/sofa/bench/left, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) +"o" = ( +/obj/structure/railing{ + dir = 4 + }, +/turf/open/floor/holofloor/stairs, +/area/template_noop) +"q" = ( +/obj/effect/turf_decal/tile/neutral{ + dir = 4 + }, +/obj/structure/table, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) +"r" = ( +/obj/effect/turf_decal/tile/neutral/half/contrasted{ + dir = 1 + }, +/obj/item/kirbyplants/random/fullysynthetic, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) +"t" = ( +/obj/structure/railing{ + dir = 8 + }, +/turf/open/floor/holofloor/stairs{ + dir = 1 + }, +/area/template_noop) +"u" = ( +/obj/effect/turf_decal/tile/neutral{ + dir = 8 + }, +/obj/structure/railing{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 1 + }, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) +"v" = ( +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 8 + }, +/turf/open/floor/holofloor{ + icon_state = "smooth_large"; + base_icon_state = "smooth_large" + }, +/area/template_noop) +"w" = ( +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 1 + }, +/turf/open/floor/holofloor{ + icon_state = "smooth_large"; + base_icon_state = "smooth_large" + }, +/area/template_noop) +"x" = ( +/obj/effect/turf_decal/tile/neutral, +/obj/structure/railing{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 1 + }, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) +"y" = ( +/obj/effect/turf_decal/tile/neutral/half, +/obj/structure/railing{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 1 + }, +/turf/open/floor/holofloor{ + base_icon_state = "smooth_half"; + icon_state = "smooth_half"; + dir = 4 + }, +/area/template_noop) +"B" = ( +/obj/effect/turf_decal/tile/neutral{ + dir = 1 + }, +/obj/structure/railing, +/obj/effect/turf_decal/trimline/yellow/warning, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) +"C" = ( +/obj/effect/turf_decal/tile/neutral/half{ + dir = 1 + }, +/obj/structure/chair/sofa/bench/solo{ + dir = 4; + pixel_y = 4 + }, +/turf/open/floor/holofloor{ + base_icon_state = "smooth_half"; + icon_state = "smooth_half"; + dir = 4 + }, +/area/template_noop) +"D" = ( +/obj/effect/turf_decal/tile/neutral{ + dir = 1 + }, +/obj/structure/chair/sofa/bench/solo{ + dir = 8; + pixel_y = 4 + }, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) +"F" = ( +/turf/open/floor/holofloor/wargame, +/area/template_noop) +"I" = ( +/obj/effect/turf_decal/arrows/white{ + dir = 1 + }, +/obj/effect/turf_decal/stripes/white/line{ + dir = 4 + }, +/obj/effect/turf_decal/stripes/white/line{ + dir = 8 + }, +/turf/open/floor/holofloor/plating, +/area/template_noop) +"J" = ( +/obj/effect/turf_decal/stripes/white/line{ + dir = 8 + }, +/obj/effect/turf_decal/stripes/white/line{ + dir = 4 + }, +/turf/open/floor/holofloor/plating, +/area/template_noop) +"L" = ( +/obj/effect/turf_decal/tile/neutral/half, +/obj/structure/chair/sofa/bench, +/turf/open/floor/holofloor{ + base_icon_state = "smooth_half"; + icon_state = "smooth_half"; + dir = 4 + }, +/area/template_noop) +"M" = ( +/obj/effect/turf_decal/tile/neutral/half, +/obj/structure/chair/sofa/bench/left, +/turf/open/floor/holofloor{ + base_icon_state = "smooth_half"; + icon_state = "smooth_half"; + dir = 4 + }, +/area/template_noop) +"O" = ( +/obj/effect/turf_decal/arrows/white, +/obj/effect/turf_decal/stripes/white/line{ + dir = 8 + }, +/obj/effect/turf_decal/stripes/white/line{ + dir = 4 + }, +/turf/open/floor/holofloor/plating, +/area/template_noop) +"P" = ( +/obj/effect/turf_decal/tile/neutral/half{ + dir = 1 + }, +/obj/structure/railing, +/obj/effect/turf_decal/trimline/yellow/warning, +/turf/open/floor/holofloor{ + base_icon_state = "smooth_half"; + icon_state = "smooth_half"; + dir = 4 + }, +/area/template_noop) +"Q" = ( +/obj/item/kirbyplants/random/fullysynthetic, +/turf/open/floor/holofloor{ + icon_state = "smooth_large"; + base_icon_state = "smooth_large" + }, +/area/template_noop) +"R" = ( +/obj/effect/turf_decal/tile/neutral/half{ + dir = 1 + }, +/obj/structure/shelf, +/obj/item/wargame_projector/terrain{ + pixel_y = -6 + }, +/obj/item/wargame_projector/ships/red{ + pixel_y = 3; + pixel_x = 3 + }, +/obj/item/wargame_projector/ships{ + pixel_y = 12 + }, +/turf/open/floor/holofloor{ + base_icon_state = "smooth_half"; + icon_state = "smooth_half"; + dir = 4 + }, +/area/template_noop) +"S" = ( +/obj/effect/turf_decal/tile/neutral, +/obj/structure/chair/sofa/bench/right, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) +"V" = ( +/obj/effect/turf_decal/arrows/white, +/obj/effect/turf_decal/stripes/white/line{ + dir = 4 + }, +/obj/effect/turf_decal/stripes/white/line{ + dir = 8 + }, +/turf/open/floor/holofloor/plating, +/area/template_noop) +"W" = ( +/obj/effect/turf_decal/stripes/white/line{ + dir = 4 + }, +/obj/effect/turf_decal/stripes/white/line{ + dir = 8 + }, +/turf/open/floor/holofloor/plating, +/area/template_noop) +"X" = ( +/obj/effect/turf_decal/trimline/yellow/corner{ + dir = 4 + }, +/turf/open/floor/holofloor{ + icon_state = "smooth_large"; + base_icon_state = "smooth_large" + }, +/area/template_noop) +"Y" = ( +/obj/effect/turf_decal/tile/neutral/half/contrasted, +/obj/structure/railing{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 1 + }, +/turf/open/floor/holofloor{ + base_icon_state = "smooth"; + icon_state = "smooth"; + dir = 1 + }, +/area/template_noop) + +(1,1,1) = {" +Q +m +o +I +W +W +V +l +X +Q +"} +(2,1,1) = {" +S +f +F +F +F +F +F +F +x +q +"} +(3,1,1) = {" +L +P +F +F +F +F +F +F +y +g +"} +(4,1,1) = {" +M +P +F +F +F +F +F +F +y +R +"} +(5,1,1) = {" +k +e +F +F +F +F +F +F +Y +r +"} +(6,1,1) = {" +d +P +F +F +F +F +F +F +y +C +"} +(7,1,1) = {" +L +P +F +F +F +F +F +F +y +h +"} +(8,1,1) = {" +n +B +F +F +F +F +F +F +u +D +"} +(9,1,1) = {" +Q +v +c +a +J +J +O +t +w +Q +"} diff --git a/modular_doppler/wargaming/code/holodeck.dm b/modular_doppler/wargaming/code/holodeck.dm new file mode 100644 index 00000000000000..d6e00b674aef61 --- /dev/null +++ b/modular_doppler/wargaming/code/holodeck.dm @@ -0,0 +1,8 @@ +/turf/open/floor/holofloor/wargame + icon = 'modular_doppler/wargaming/icons/turf.dmi' + icon_state = "holodeck" + +/datum/map_template/holodeck/doppler_wargame_board + name = "Holodeck - Wargame Simulator" + template_id = "holodeck_doppler_wargame_board" + mappath = "_maps/templates/holodeck_doppler_wargame_board.dmm" diff --git a/modular_doppler/wargaming/icons/turf.dmi b/modular_doppler/wargaming/icons/turf.dmi new file mode 100644 index 0000000000000000000000000000000000000000..d1fb9d9a8bba32aa16b9b24693449960d9b13b17 GIT binary patch literal 389 zcmV;00eb$4P)V=-0C=2JR&a84_w-Y6@%7{?OD!tS%+FJ>RWQ*r;NmRLOex6#a*U0*I5Sc+ z(=$pSoZ^zil2jm5DI-58KP5FeTZxM^C9|j)C}haRnO2mTn+jnoE4cc(fK3JfncN#x zJgOl!0002gNklqf>N%j>4TGsC;mQKBd3m z3-#hJp*X}ZfGr27;eMN6k7u`6m*Lted`7rG_f2z^&-Mk1R6e_5Z)VptPSsxOsP$Az znbFalex0LQd76T64>PHJ#v2G=DA|Use60e{%Q8QoM)Xnn#LfYhb9N3eew6|iGF(^r z)j`BIM5%nOg1vRD@+WujP;KSxsCAkGCY7gEz$E_-eG?yBZI%Cj1fm~sBl>|YML&RJ j^aIfkL_dIven92lq>fvJ*{r=t00000NkvXXu0mjfq4K5V literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index ebcd58259bceab..2e614c4d43b20f 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -7956,6 +7956,7 @@ #include "modular_doppler\verbs\code\subtle.dm" #include "modular_doppler\wargaming\code\base_station.dm" #include "modular_doppler\wargaming\code\game_kit.dm" +#include "modular_doppler\wargaming\code\holodeck.dm" #include "modular_doppler\wargaming\code\projectors.dm" #include "modular_doppler\wargaming\code\team_datums.dm" #include "modular_doppler\wargaming\code\conditions\_unit_conditions.dm" From 4c5f6735884a058237169cd10bd6cae9d9354ff4 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 00:03:23 -0400 Subject: [PATCH 07/36] removes that file --- modular_doppler/wargaming/code/controller.dm | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 modular_doppler/wargaming/code/controller.dm diff --git a/modular_doppler/wargaming/code/controller.dm b/modular_doppler/wargaming/code/controller.dm deleted file mode 100644 index ac07ee08fecdeb..00000000000000 --- a/modular_doppler/wargaming/code/controller.dm +++ /dev/null @@ -1,3 +0,0 @@ -/obj/item/wargame_controller - name = "wargaming controller" - desc = "A handheld controller for controlling holograms. Must be linked to a base station in order to work." From b7c9403b9774c667c1cb46f4fa9af5765b3a84b6 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 09:36:27 -0400 Subject: [PATCH 08/36] updates projector sprite --- modular_doppler/wargaming/icons/projector.dmi | Bin 462 -> 688 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/modular_doppler/wargaming/icons/projector.dmi b/modular_doppler/wargaming/icons/projector.dmi index 397e586d892142490f3cf57e22a791a2d6fca086..0847d1ce95689b6e70c1ae34d55dada002b7867a 100644 GIT binary patch delta 531 zcmV+u0_^?H1F!{9fXnzSP00t|-n*~%=)No=D zASU)nQ8y3(X9^%x(u3rQjoHDR0`O)5VnI)10|PfVH-`AvuU{ET>KmwG1k+p_`!(6(A>t;0%DLPoIGEFvt@y8U|o|AxUcP>g z5)cMv#_&cCwie;ZL%YEAkZlTs0HKl`Xhr_Tq!CC<3*-T-g95M=l^30H9cGnOsg(jj(C+5wabE50sT9 z(X0R^Dg}`1F;L)vFeoM5dvu;GGiVKP1^~$6e!ezj8;q_OSd)-#7A*nJ0HCDmLf1=a zHAqWAIEM!_f;1*2fZ0sx-=Qg^MFZ`uF=002ovPDHLkV1lrd Bda?ij From 717efce32e64d027aaf7eb8544c65d0b685fe72d Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 09:38:05 -0400 Subject: [PATCH 09/36] makes asteroids get the right conditions when damaged --- modular_doppler/wargaming/code/unit_stats/asteroid.dm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modular_doppler/wargaming/code/unit_stats/asteroid.dm b/modular_doppler/wargaming/code/unit_stats/asteroid.dm index 31334531b8a92f..837e1b4ec8e2ab 100644 --- a/modular_doppler/wargaming/code/unit_stats/asteroid.dm +++ b/modular_doppler/wargaming/code/unit_stats/asteroid.dm @@ -2,9 +2,7 @@ abstract_type = /datum/wargame_unit_stats/terrain conditions_limit = 4 possible_conditions = list( - /datum/wargame_condition/hull_damage, - /datum/wargame_condition/engine_damage, - /datum/wargame_condition/reactor_damage, + /datum/wargame_condition/hull_damage/asteroid, ) is_small_vessel = FALSE unit_size = WARGAME_SIZE_LARGE From bd1ee321d52fd18a5f4fe0c1ddd2e24effd6a5f8 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 09:54:12 -0400 Subject: [PATCH 10/36] re-organizes icon files --- modular_doppler/wargaming/code/base_station.dm | 14 ++++++++++++++ modular_doppler/wargaming/code/projectors.dm | 2 +- modular_doppler/wargaming/icons/items.dmi | Bin 478 -> 1214 bytes modular_doppler/wargaming/icons/projector.dmi | Bin 688 -> 0 bytes 4 files changed, 15 insertions(+), 1 deletion(-) delete mode 100644 modular_doppler/wargaming/icons/projector.dmi diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index b418fdf0ba7aa3..1c3880850e1f06 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -40,6 +40,20 @@ BASESTATION_TEAM_COLOR = image(icon = WARGAME_ACTIONS_FILE, icon_state = "team_color"), ) +/obj/item/wargame_base_station/Initialize(mapload) + . = ..() + if(prob(0.01)) + make_special_edition() + +/// Turns the console gold and gives it a silly name +/obj/item/wargame_base_station/proc/make_special_edition() + name = "\improper DopplerStation 2" + desc = "A base station for holographic wargames, all controllers and interactive wearables link to this machine and are automatically managed by it. \ + This looks like the special pre-order edition of the console, how did one of these get out this far? The faceplate has been replaced with one \ + plated in gold, and someone seems to have left a sticker of a little dolphin on the case." + icon_state = "basestation_gold" + update_appearance(UPDATE_ICON) + /obj/item/wargame_base_station/examine(mob/user) . = ..() . += span_notice("Interact with the menu with [EXAMINE_HINT("Alt-Click")].") diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index 22b4f60757e272..1d627a2df0799e 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -1,7 +1,7 @@ /obj/item/wargame_projector name = "holographic wargame projector" desc = "A holographic projectors for creating holograms that work in the wargaming system." - icon = 'modular_doppler/wargaming/icons/projector.dmi' + icon = 'modular_doppler/wargaming/icons/items.dmi' icon_state = "projector" base_icon_state = "projector" lefthand_file = 'modular_doppler/wargaming/icons/mob/lefthand.dmi' diff --git a/modular_doppler/wargaming/icons/items.dmi b/modular_doppler/wargaming/icons/items.dmi index b2eb53e950e2e7a33474502f01ad9cdf803dfcaa..486a174f99ce0a8c0d9c7aafc617ea15d937c309 100644 GIT binary patch delta 1132 zcmV-y1e5#T1HK6*iBL{Q4GJ0x0000DNk~Le0000$0000$2nGNE0IF$m-T(jqiIF8^ zf7sNxR@`Y^xaJ;9>2_KJ)e^JW;zw&C?lr`;e+6q5SmaNzxqO{Q|2bRnE2rB?O zcW6($lP2RdtQjC$Js0R)vg4eSPImBqF>Dxv{Cb5IK#nM@^UvPoJ@?CCc|?cpIH?FX z3$KTX`}qV~0tj|8F0hj_2<-^P3CsZ2EC@nfDnKT6qkt#VpOxGn$_CItf5S&U4xc)+y*$CS_f<+I)UD)R1*zOuvKQ~RZ~omvUH zyDXw`dsS062O_FqD!}g*H2E58C7@?O+zp5ut`TItglyhu8V3#^;|h84blk{|Pt>Hr zWS$Aq);i>)654iWxk5kzwF+|yz-K753lJIoAos-qbq4S(J(P=%e{3z!04c}fH?9pB ziA2JazqGVuT)H|SLxeJ@3V=24{u0bx4o2`_+Itvjnx@oG y3HL;(Gke5ShX2-B3CH5xr$E$yh0$mF6XV59>^QWPsGDgB4B1q1{!4fSv=;eT-~5%=g6P1fm^~ znOO{Xz|NiP;SPZTn5FLSfpD?u(|ZY;&qAyNK)^5DA5L#txe=#42w;<^$N?b3J?w4a zd}KB*01N@@IRM#5_$=RX@GypYsyG0fCDZ_97eu2DpcWu24d4Q>gom6R>FNMjSOGV=-0C=2JR&a84_w-Y6@%7{?OD!tS%+FJ>RWQ*r;NmRLOex6#a*U0*I5Sc+ z(=$pSoZ^zil2jm5sh}u7D>b!vF>M;qM20005=NklKmwG1k+< z!xsDa#K{H!fdE7^0s*=ldY=KrPbR`ls$>Cr0a!3hb1q1{H!5yB%hYv8kWI!$a zkh{Y}9pLQ^B6oeUfph21(!^2p1O!Kf+}*q| Date: Sat, 23 May 2026 09:57:50 -0400 Subject: [PATCH 11/36] adds interaction hints --- modular_doppler/wargaming/code/base_station.dm | 6 ++++++ modular_doppler/wargaming/code/holograms/_holograms.dm | 6 ++++++ modular_doppler/wargaming/code/projectors.dm | 9 +++++++++ 3 files changed, 21 insertions(+) diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index 1c3880850e1f06..7cbd624b59e688 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -42,9 +42,15 @@ /obj/item/wargame_base_station/Initialize(mapload) . = ..() + register_context() if(prob(0.01)) make_special_edition() +/obj/item/wargame_base_station/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + if(isnull(held_item)) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Open console menu" + return CONTEXTUAL_SCREENTIP_SET + /// Turns the console gold and gives it a silly name /obj/item/wargame_base_station/proc/make_special_edition() name = "\improper DopplerStation 2" diff --git a/modular_doppler/wargaming/code/holograms/_holograms.dm b/modular_doppler/wargaming/code/holograms/_holograms.dm index e0e83621885042..5267f6db692a80 100644 --- a/modular_doppler/wargaming/code/holograms/_holograms.dm +++ b/modular_doppler/wargaming/code/holograms/_holograms.dm @@ -30,6 +30,12 @@ if(swarming) AddComponent(/datum/component/swarming, max_x = 12, max_y = 12) layer = LOW_ITEM_LAYER + register_context() + +/obj/item/wargame_projector/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + if(isnull(held_item)) + context[SCREENTIP_CONTEXT_LMB] = "Open actions menu" + return CONTEXTUAL_SCREENTIP_SET /obj/structure/wargame_hologram/examine(mob/user) . = ..() diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index 1d627a2df0799e..e854c775ac8685 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -50,6 +50,15 @@ . = ..() update_appearance() populate_radial_choice_lists() + register_context() + +/obj/item/wargame_projector/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + if(isnull(held_item)) + context[SCREENTIP_CONTEXT_LMB] = "Change hologram" + if(!requires_linked_team) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Change hologram color" + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Clear all holograms" + return CONTEXTUAL_SCREENTIP_SET /obj/item/wargame_projector/update_appearance() . = ..() From 04b43ecec8bc5b5c676f6a31fb7ebb8ffaba9155 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 10:14:35 -0400 Subject: [PATCH 12/36] adds clicking sounds to the projector and base station --- .../wargaming/code/base_station.dm | 34 +++++++++++++++++++ modular_doppler/wargaming/code/projectors.dm | 23 ++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index 7cbd624b59e688..a45c8ff414d69b 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -8,6 +8,8 @@ inhand_icon_state = "generic" w_class = WEIGHT_CLASS_SMALL item_flags = NOBLUDGEON + drop_sound = 'sound/items/handling/tools/rcd_drop.ogg' + pickup_sound = 'sound/items/handling/tools/rcd_pickup.ogg' /// List of all team datums this controller is currently managing, empty teams are cleared at game start var/list/managed_teams = list() /// Nebulous list of teams per round, subtracted from as teams complete their turns @@ -106,6 +108,7 @@ end_the_game(user) if(BASESTATION_NEXT) advance_phase(user) + play_menu_sound() return CLICK_ACTION_SUCCESS /// Converts a given game phase number to text @@ -159,28 +162,35 @@ var/option = tgui_alert(user, "Join or leave a team?", "Team Manager", list(JOIN_A_TEAM, LEAVE_A_TEAM)) if(isnull(option)) balloon_alert(user, "no choice made!") + play_menu_sound() return switch(option) if(JOIN_A_TEAM) var/picked_team = tgui_input_list(user, "Choose which team to join.", "Team Manager", managed_teams) if(isnull(picked_team)) balloon_alert(user, "no choice made!") + play_menu_sound() return var/datum/wargaming_team/wargame_team = managed_teams[picked_team] if(user in wargame_team.team_players) balloon_alert(user, "already in!") + play_menu_sound() return wargame_team.team_players += user + play_menu_sound() if(LEAVE_A_TEAM) var/picked_team = tgui_input_list(user, "Choose which team to leave.", "Team Manager", managed_teams) if(isnull(picked_team)) balloon_alert(user, "no choice made!") + play_menu_sound() return var/datum/wargaming_team/wargame_team = managed_teams[picked_team] if(!(user in wargame_team.team_players)) balloon_alert(user, "not in team!") + play_menu_sound() return wargame_team.team_players -= user + play_menu_sound() #undef JOIN_A_TEAM #undef LEAVE_A_TEAM @@ -220,6 +230,7 @@ teams_per_turn -= team_turn game_phase = WARGAME_PHASE_ACTION say("Action phase, [team_turn.team_name], turn [turn_counter].") + play_menu_sound() /// Ends the game if it is currently running /obj/item/wargame_base_station/proc/end_the_game(mob/living/user) @@ -228,8 +239,10 @@ return var/certainty = tgui_alert(user, "Are you certain you wish to end the game?", "Game Ender", list(MY_CHILD_WILL, MY_CHILD_WILL_NOT)) if(certainty != MY_CHILD_WILL) + play_menu_sound() return game_phase = WARGAME_PHASE_NOTHING + play_menu_sound() say("Game ended.") /// Starts the game if it has not already been started @@ -247,6 +260,7 @@ return var/certainty = tgui_alert(user, "Are you certain you wish to start the game?", "Game Starter", list(MY_CHILD_WILL, MY_CHILD_WILL_NOT)) if(certainty != MY_CHILD_WILL) + play_menu_sound() return game_phase = WARGAME_PHASE_PLACEMENT for(var/team as anything in managed_teams) @@ -260,6 +274,7 @@ team_turn.ready_all_units() teams_per_turn -= team_turn turn_counter = 1 + play_menu_sound() say("Beginning game, placement phase.") /// Returns a list of just team datums from the managed_teams list @@ -276,13 +291,16 @@ for(var/team as anything in managed_teams) qdel(managed_teams[team]) managed_teams -= team + play_menu_sound() /// Selects a team and edits the name or color /obj/item/wargame_base_station/proc/edit_a_team(mob/living/user) var/editing_team = tgui_input_list(user, "Choose which team you wish to edit.", "Team List", managed_teams) if(isnull(editing_team)) balloon_alert(user, "no choice!") + play_menu_sound() return + play_menu_sound() show_team_edit_radial(user, managed_teams[editing_team]) /// Constantly shows a radial for editing a team until cancelled @@ -296,18 +314,23 @@ if(isnull(new_name)) balloon_alert(user, "needs name!") show_team_edit_radial(user, edited_team) + play_menu_sound() return if(new_name in managed_teams) balloon_alert(user, "name in use!") show_team_edit_radial(user, edited_team) + play_menu_sound() return + play_menu_sound() edited_team.team_name = new_name if(BASESTATION_TEAM_COLOR) var/new_color = input(user, "Choose your new team color." ,"Color Selection", COLOR_PRIDE_PURPLE) as color|null if(isnull(new_color)) balloon_alert(user, "needs color!") show_team_edit_radial(user, edited_team) + play_menu_sound() return + play_menu_sound() edited_team.team_color = new_color /// Lets players pick from a list of existing teams to delete one @@ -315,11 +338,13 @@ var/deleting_team = tgui_input_list(user, "Choose which team you wish to delete.", "Team List", managed_teams) if(isnull(deleting_team)) balloon_alert(user, "no choice!") + play_menu_sound() return var/certainty = tgui_alert(user, "Are you certain you wish to delete [deleting_team]?", "Deletion Confirmation", list(MY_CHILD_WILL, MY_CHILD_WILL_NOT)) if(certainty == MY_CHILD_WILL) qdel(managed_teams[deleting_team]) managed_teams -= deleting_team + play_menu_sound() /// Interactively create a new team with the user /obj/item/wargame_base_station/proc/create_new_team(mob/living/user) @@ -328,19 +353,28 @@ if(isnull(new_name)) // How? qdel(new_team) balloon_alert(user, "needs name!") + play_menu_sound() return if(new_name in managed_teams) // This name is already in use, get some new material qdel(new_team) balloon_alert(user, "name in use!") + play_menu_sound() return + play_menu_sound() new_team.team_name = new_name var/new_color = input(user, "Choose your new team color." ,"Color Selection", COLOR_PRIDE_PURPLE) as color|null if(isnull(new_color)) qdel(new_team) balloon_alert(user, "needs color!") + play_menu_sound() return + play_menu_sound() new_team.team_color = new_color managed_teams[new_team.team_name] += new_team +/// Plays a clicking sound for menu actions +/obj/item/wargame_base_station/proc/play_menu_sound() + playsound(src, SFX_REMOTE_MODE_SWITCH, 50, TRUE) + #undef MY_CHILD_WILL #undef MY_CHILD_WILL_NOT diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index e854c775ac8685..af4f2aa6c50c7a 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -11,6 +11,8 @@ worn_icon_state = "generic" w_class = WEIGHT_CLASS_SMALL item_flags = NOBLUDGEON + pickup_sound = SFX_GENERIC_DEVICE_PICKUP + drop_sound = SFX_GENERIC_DEVICE_DROP /// All of the signs this projector is maintaining var/list/projections /// The color to give holograms when created @@ -74,6 +76,10 @@ . += span_notice("Alt clicking the projector will let you change the color of the next hologram it makes.") . += span_warning("Control clicking the projector will allow you to clear all active holograms.") +/// Plays a clicking sound for menu actions +/obj/item/wargame_projector/proc/play_menu_sound() + playsound(src, SFX_REMOTE_ACTION, 50, TRUE) + /obj/item/wargame_projector/proc/populate_radial_choice_lists() if(!length(radial_choices) || !length(projection_names_to_path)) for(var/obj/structure/wargame_hologram/hologram as anything in holosign_options) @@ -92,6 +98,7 @@ if(isnull(picked_choice)) return holosign_type = projection_names_to_path[picked_choice] + play_menu_sound() /obj/item/wargame_projector/attack_self(mob/user) select_hologram(user) @@ -102,10 +109,12 @@ var/selected_color = tgui_input_list(user, "Select a color", "Color Selection", color_options) if(isnull(selected_color)) balloon_alert(user, "no color change") + play_menu_sound() return var/color_to_set_to = color_options[selected_color] holosign_color = color_to_set_to balloon_alert(user, "color changed") + play_menu_sound() update_appearance() return CLICK_ACTION_SUCCESS @@ -113,6 +122,7 @@ if(tgui_alert(usr,"Clear all currently active holograms?", "Hologram Removal", list("Yes", "No")) == "Yes") for(var/hologram as anything in projections) qdel(hologram) + play_menu_sound() return CLICK_ACTION_SUCCESS /// Can we place a hologram at the target location? @@ -135,7 +145,7 @@ /obj/item/wargame_projector/proc/create_hologram(atom/target, mob/user, list/modifiers) var/obj/structure/wargame_hologram/target_holosign = new holosign_type(get_turf(target), src) target_holosign.color = holosign_color - playsound(loc, 'sound/machines/click.ogg', 20, TRUE) + playsound(loc, SFX_INDUSTRIAL_SCAN, 45, TRUE) if(requires_linked_team) target_holosign.team_reference = linked_team if(target_holosign.swarming) @@ -157,21 +167,32 @@ var/picked_team = tgui_input_list(user, "Pick a team to link to.", "Team Picker", base_station.managed_teams) if(isnull(picked_team)) base_station.balloon_alert(user, "needs team!") + play_menu_sound() return NONE reference_team = base_station.managed_teams[picked_team] reference_team.tracked_projectors += src linked_team = WEAKREF(reference_team) if(!do_after(user, 3 SECONDS, base_station)) + play_menu_sound() return NONE if(!isnull(reference_team)) holosign_color = reference_team.team_color update_appearance() + playsound(src, SFX_INDUSTRIAL_SCAN, 50, TRUE) linked_base_station = WEAKREF(base_station) return ITEM_INTERACT_SUCCESS if(istype(interacting_with, /obj/structure/wargame_hologram)) + var/obj/item/wargame_base_station/base_station = linked_base_station?.resolve() + if(!isnull(base_station)) + if(base_station.game_phase != WARGAME_PHASE_PLACEMENT) + user.balloon_alert(user, "wrong game phase!") + play_menu_sound() + return NONE qdel(interacting_with) + play_menu_sound() return ITEM_INTERACT_SUCCESS if(!check_can_place_hologram(interacting_with, user)) + play_menu_sound() return NONE create_hologram(interacting_with, user, modifiers) return ITEM_INTERACT_SUCCESS From 09c2b7db4ff0355262c27290d9f2efdb879ce451 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 10:24:21 -0400 Subject: [PATCH 13/36] upgrades autocannon sfx --- .../wargaming/code/unit_stats/_unit_stats.dm | 1 + .../wargaming/code/unit_weapons/small_guns.dm | 4 ++-- .../wargaming/sound/autocannonlong.ogg | Bin 0 -> 49115 bytes 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 modular_doppler/wargaming/sound/autocannonlong.ogg diff --git a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm index 3509b55e4469e0..7a17a41a705d78 100644 --- a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -136,6 +136,7 @@ return if(weapon_choice.firing_voiceline() && talkative) hologram.say(weapon_choice.firing_voiceline()) + weapon_choice.weapon_firing_sound(hologram) if(!weapon_choice.all_special_effects) var/impact = target_hologram.unit_stats.get_attacked(user, hologram, target_hologram, weapon_choice) weapon_choice.special_effects_fire(user, src, hologram, target_hologram, impact) diff --git a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm index 985c710becbd45..5c81db7f143e1e 100644 --- a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm +++ b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm @@ -8,7 +8,7 @@ action_point_cost = 1 /datum/wargame_weapon/autocannon/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/autocannon.ogg', 75, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/autocannonlong.ogg', 75, TRUE) /datum/wargame_weapon/autocannon/weapon_description() return "Low calibre 45mm autocannons for fast moving targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." @@ -31,7 +31,7 @@ action_point_cost = 1 /datum/wargame_weapon/pdc/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/autocannon.ogg', 75, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/autocannonlong.ogg', 75, TRUE) /datum/wargame_weapon/pdc/weapon_description() return "Small calibre 26mm autocannons with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." diff --git a/modular_doppler/wargaming/sound/autocannonlong.ogg b/modular_doppler/wargaming/sound/autocannonlong.ogg new file mode 100644 index 0000000000000000000000000000000000000000..f57538a9a2eb1c0227124bef53997298e33b2c09 GIT binary patch literal 49115 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 Date: Sat, 23 May 2026 11:08:40 -0400 Subject: [PATCH 14/36] adds more flavour to attacks --- .../holodeck_doppler_wargame_board.dmm | 17 ++++++++++++----- .../wargaming/code/unit_stats/_unit_stats.dm | 1 + .../code/unit_weapons/_unit_weapons.dm | 4 ++++ .../wargaming/code/unit_weapons/beams.dm | 6 ++++++ .../wargaming/code/unit_weapons/big_guns.dm | 9 +++++++++ .../wargaming/code/unit_weapons/bombs.dm | 10 ++++++++-- .../wargaming/code/unit_weapons/missiles.dm | 18 ++++++++++++++++++ .../wargaming/code/unit_weapons/ramming.dm | 3 +++ .../wargaming/code/unit_weapons/small_guns.dm | 6 ++++++ 9 files changed, 67 insertions(+), 7 deletions(-) diff --git a/_maps/templates/holodeck_doppler_wargame_board.dmm b/_maps/templates/holodeck_doppler_wargame_board.dmm index d5fe3fa7b30969..0e6e76a81c322d 100644 --- a/_maps/templates/holodeck_doppler_wargame_board.dmm +++ b/_maps/templates/holodeck_doppler_wargame_board.dmm @@ -121,6 +121,17 @@ dir = 4 }, /obj/structure/table, +/obj/item/wargame_projector/terrain{ + pixel_y = 6; + pixel_x = 14 + }, +/obj/item/clothing/head/tajaran_hat{ + pixel_y = 8; + pixel_x = -4 + }, +/obj/item/clothing/head/lizard_hat/white{ + pixel_x = -5 + }, /turf/open/floor/holofloor{ base_icon_state = "smooth"; icon_state = "smooth"; @@ -324,12 +335,8 @@ dir = 1 }, /obj/structure/shelf, -/obj/item/wargame_projector/terrain{ - pixel_y = -6 - }, /obj/item/wargame_projector/ships/red{ - pixel_y = 3; - pixel_x = 3 + pixel_x = 9 }, /obj/item/wargame_projector/ships{ pixel_y = 12 diff --git a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm index 7a17a41a705d78..6926c31db76f14 100644 --- a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -136,6 +136,7 @@ return if(weapon_choice.firing_voiceline() && talkative) hologram.say(weapon_choice.firing_voiceline()) + weapon_choice.weapon_firing_message(hologram, target_hologram) weapon_choice.weapon_firing_sound(hologram) if(!weapon_choice.all_special_effects) var/impact = target_hologram.unit_stats.get_attacked(user, hologram, target_hologram, weapon_choice) diff --git a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm index ca6ac95f828c08..ac6a2c794adc28 100644 --- a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm +++ b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm @@ -33,6 +33,10 @@ /datum/wargame_weapon/proc/weapon_firing_sound(obj/firer) return +/// Plays a firing sound for the weapon +/datum/wargame_weapon/proc/weapon_firing_message(obj/firer, obj/target) + return + /// By default, checks in a circle range around the ship for another hologram to target /datum/wargame_weapon/proc/pick_target(mob/living/user, obj/structure/wargame_hologram/hologram) var/datum/wargaming_team/hologram_team = hologram.team_reference?.resolve() diff --git a/modular_doppler/wargaming/code/unit_weapons/beams.dm b/modular_doppler/wargaming/code/unit_weapons/beams.dm index 197cf2d2c3889c..9c576832f7f8f1 100644 --- a/modular_doppler/wargaming/code/unit_weapons/beams.dm +++ b/modular_doppler/wargaming/code/unit_weapons/beams.dm @@ -14,6 +14,9 @@ /datum/wargame_weapon/anti_ship_beam/weapon_description() return "A high-energy beam for coring large ships with. Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/anti_ship_beam/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer]'s spinal beam lens glows before piercing the sky towards [target] with a blinding flash of light!")) + /datum/wargame_weapon/anti_ship_beam/firing_voiceline() var/static/list/lines = list( "Try not to stare directly at the beam.", @@ -37,6 +40,9 @@ /datum/wargame_weapon/pd_beam/weapon_description() return "Highly focused beams with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/pd_beam/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("Gimbaled lenses all along [firer] turn towards [target] and light up the night with a dazzling laser light show!")) + /datum/wargame_weapon/pd_beam/firing_voiceline() var/static/list/lines = list( "Incoming track within range, blink it with the lasers.", diff --git a/modular_doppler/wargaming/code/unit_weapons/big_guns.dm b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm index c8b19f4adc76be..8ff1078c789e7a 100644 --- a/modular_doppler/wargaming/code/unit_weapons/big_guns.dm +++ b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm @@ -14,6 +14,9 @@ /datum/wargame_weapon/mass_driver/weapon_description() return "A large mass driver for launching hardened cores at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/mass_driver/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer]'s mass driver ripples the space around it as it charges, creating a streak across the sky as it fires at [target]!")) + /datum/wargame_weapon/mass_driver/firing_voiceline() var/static/list/lines = list( "We shot this in atmosphere once, you should've seen it!", @@ -40,6 +43,9 @@ /datum/wargame_weapon/large_cannon/weapon_description() return "A large calibre 7 in. cannon for launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/large_cannon/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer]'s 7\" guns fire at [target], sending a jet of burning plasma out the back of each cannon!")) + /datum/wargame_weapon/large_cannon/firing_voiceline() var/static/list/lines = list( "Target lead and trajectory calculations green, fire at will.", @@ -64,6 +70,9 @@ /datum/wargame_weapon/medium_cannon/weapon_description() return "A medium calibre 2.5 in. cannon fore launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/medium_cannon/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer]'s 2.5\" guns turn to bear on [target], rattling the vessel as they unleash a volley of shells!")) + /datum/wargame_weapon/medium_cannon/firing_voiceline() var/static/list/lines = list( "All mounts local control, watch fire for friendlies!", diff --git a/modular_doppler/wargaming/code/unit_weapons/bombs.dm b/modular_doppler/wargaming/code/unit_weapons/bombs.dm index bbe275e9fdb3ec..ed3385b4056f8b 100644 --- a/modular_doppler/wargaming/code/unit_weapons/bombs.dm +++ b/modular_doppler/wargaming/code/unit_weapons/bombs.dm @@ -8,11 +8,14 @@ action_point_cost = 1 /datum/wargame_weapon/rockets/strike - maximum_ammo = 2 + maximum_ammo = 4 /datum/wargame_weapon/rockets/weapon_description() return "A barrage of unguided rockets from a fixed rack or large launcher. Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/rockets/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer] lets loose a volley of rockets at [target]!")) + /datum/wargame_weapon/rockets/firing_voiceline() var/static/list/lines = list( "Go for rocket salvo, let's see how they like some of these!", @@ -29,12 +32,15 @@ evadable = TRUE small_ship_disadvantage = TRUE radial_icon_state = "weapon_bomb" - maximum_ammo = 1 + maximum_ammo = 2 action_point_cost = 1 /datum/wargame_weapon/bombs/weapon_description() return "A group of unpowered explosive bombs to crack large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/bombs/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer] makes a run at [target], releasing a large bomb that drifts toward them!")) + /datum/wargame_weapon/bombs/firing_voiceline() var/static/list/lines = list( "I got a present for ya!", diff --git a/modular_doppler/wargaming/code/unit_weapons/missiles.dm b/modular_doppler/wargaming/code/unit_weapons/missiles.dm index 40e4902b7b4832..81c131cd92ca16 100644 --- a/modular_doppler/wargaming/code/unit_weapons/missiles.dm +++ b/modular_doppler/wargaming/code/unit_weapons/missiles.dm @@ -12,6 +12,9 @@ /datum/wargame_weapon/missile/cruise/weapon_firing_sound(obj/firer) playsound(firer, 'modular_doppler/wargaming/sound/missile_launch.ogg', 50, TRUE) +/datum/wargame_weapon/missile/cruise/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer]'s missile tubes pop open and dispense a large missile that quickly turns and burns towards [target]!")) + /datum/wargame_weapon/missile/cruise/weapon_description() return "A long range cruise missile for engaging large targets. Becomes a controllable ship if the target is beyond short range. \ Max target range of [attack_range] tiles. [action_point_cost] AP to fire." @@ -36,6 +39,9 @@ return "Guide the cruise missile into a target. This is a one way trip. \ Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/ramming/cruise/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer] engages its terminal stage boosters as it dives toward [target] in a spiraling loop!")) + /datum/wargame_weapon/missile/swarm weapon_name = "Swarm Missiles" attack_roll = "5d4+2" @@ -53,6 +59,9 @@ return "A swarm of small missiles for intercepting other missiles or strike craft. Becomes a controllable ship if the target is beyond short range. \ Max target range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/missile/swarm/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer] ejects a bouquet of micro-missiles with a puff of gas, which break off into a swarm towards [target]!")) + /datum/wargame_weapon/missile/swarm/firing_voiceline() var/static/list/lines = list( "Get some swarmers on that track!", @@ -72,6 +81,9 @@ return "Guide the swarm missiles into a target. This is a one way trip. \ Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/ramming/swarm/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer] swarm towards [target], burning in high-gee turns to evade defense!")) + /datum/wargame_weapon/missile/torpedo weapon_name = "Torpedo" attack_roll = "2d20+4" @@ -90,6 +102,9 @@ return "A beefy torpedo for cracking large ships in two. Becomes a controllable ship if the target is beyond short range. \ Max target range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/missile/torpedo/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer] dispenses a large torpedo, which slowly spins up and gimbals toward [target]!")) + /datum/wargame_weapon/missile/torpedo/firing_voiceline() var/static/list/lines = list( "The guns ain't enough. I want that track gone.", @@ -109,3 +124,6 @@ /datum/wargame_weapon/ramming/torpedo/weapon_description() return "Guide the torpedo into a target. This is a one way trip. \ Max range of [attack_range] tiles. [action_point_cost] AP to fire." + +/datum/wargame_weapon/ramming/torpedo/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer] ejects its cruise stage and ignites an oversized terminal booster, speeding towards [target]!")) diff --git a/modular_doppler/wargaming/code/unit_weapons/ramming.dm b/modular_doppler/wargaming/code/unit_weapons/ramming.dm index 30ea425d069da0..89cbedfab744cb 100644 --- a/modular_doppler/wargaming/code/unit_weapons/ramming.dm +++ b/modular_doppler/wargaming/code/unit_weapons/ramming.dm @@ -13,3 +13,6 @@ "Ah.", ) return pick(lines) + +/datum/wargame_weapon/ramming/strike/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer] locks into a collision trajectory with [target], engines burning to full!")) diff --git a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm index 5c81db7f143e1e..8472a8134e1a95 100644 --- a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm +++ b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm @@ -13,6 +13,9 @@ /datum/wargame_weapon/autocannon/weapon_description() return "Low calibre 45mm autocannons for fast moving targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/autocannon/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer]'s 45mm autocannon tracks toward [target], following the target for mere moments before sending a burst of shells at it!")) + /datum/wargame_weapon/autocannon/firing_voiceline() var/static/list/lines = list( "Tracking looks good, let's let 'em have some.", @@ -36,6 +39,9 @@ /datum/wargame_weapon/pdc/weapon_description() return "Small calibre 26mm autocannons with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/pdc/weapon_firing_message(obj/firer, obj/target) + firer.visible_message(span_warning("[firer]'s point defence grid quickly gimbals toward [target], filling the sky with a dense rain of tracers!")) + /datum/wargame_weapon/pdc/firing_voiceline() var/static/list/lines = list( "We need PDC on that target, now!", From dd9db5c207d80d77b32ca3303b038132869613c0 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 11:39:27 -0400 Subject: [PATCH 15/36] fixes short range missiles --- code/datums/chatmessage.dm | 2 +- .../preferences/chat_color.dm | 4 ++++ modular_doppler/wargaming/code/base_station.dm | 15 ++++++++++++++- .../wargaming/code/conditions/_unit_conditions.dm | 4 ++-- .../wargaming/code/holograms/_holograms.dm | 3 ++- modular_doppler/wargaming/code/projectors.dm | 1 + .../wargaming/code/unit_weapons/_unit_weapons.dm | 2 +- .../wargaming/code/unit_weapons/beams.dm | 4 ++-- .../wargaming/code/unit_weapons/big_guns.dm | 6 +++--- .../wargaming/code/unit_weapons/bombs.dm | 6 ++++++ .../wargaming/code/unit_weapons/missiles.dm | 6 +++--- .../wargaming/code/unit_weapons/small_guns.dm | 4 ++-- 12 files changed, 41 insertions(+), 16 deletions(-) diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm index 8c4753644bbd84..0dc8596a2f0ad3 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -164,7 +164,7 @@ chat_color_name_to_use = target.get_voice() // for everything else, use the target's voice name // Calculate target color if not already present - if (!target.chat_color || target.chat_color_name != chat_color_name_to_use) + if ((!target.chat_color || target.chat_color_name != chat_color_name_to_use) && !target.do_not_override_chat_colors) // DOPPLER EDIT - Adds option to not override chat colors - ORIGINAL: if (!target.chat_color || target.chat_color_name != chat_color_name_to_use) target.chat_color = get_chat_color_string(chat_color_name_to_use) // DOPPLER EDIT CHANGE - ORIGINAL: target.chat_color = colorize_string(chat_color_name_to_use) target.chat_color_darkened = get_chat_color_string(chat_color_name_to_use, darkened = TRUE) // DOPPLER EDIT CHANGE - ORIGINAL: target.chat_color_darkened = colorize_string(chat_color_name_to_use, 0.85, 0.85) target.chat_color_name = chat_color_name_to_use diff --git a/modular_doppler/modular_customization/preferences/chat_color.dm b/modular_doppler/modular_customization/preferences/chat_color.dm index b8c117c2de55c2..5f3d317dcf6023 100644 --- a/modular_doppler/modular_customization/preferences/chat_color.dm +++ b/modular_doppler/modular_customization/preferences/chat_color.dm @@ -1,3 +1,7 @@ +/atom + /// Tells the chat message color system to not override this thing's chat color with the cache + var/do_not_override_chat_colors = FALSE + /datum/preference/color/chat_color category = PREFERENCE_CATEGORY_NON_CONTEXTUAL priority = PREFERENCE_PRIORITY_NAME_MODIFICATIONS diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index a45c8ff414d69b..706cadf82b024f 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -6,10 +6,14 @@ lefthand_file = 'modular_doppler/wargaming/icons/mob/lefthand.dmi' righthand_file = 'modular_doppler/wargaming/icons/mob/righthand.dmi' inhand_icon_state = "generic" + worn_icon = 'modular_doppler/wargaming/icons/mob/worn.dmi' + worn_icon_state = "controller" w_class = WEIGHT_CLASS_SMALL item_flags = NOBLUDGEON drop_sound = 'sound/items/handling/tools/rcd_drop.ogg' pickup_sound = 'sound/items/handling/tools/rcd_pickup.ogg' + do_not_override_chat_colors = TRUE + chat_color = LIGHT_COLOR_COPPER_OXIDE /// List of all team datums this controller is currently managing, empty teams are cleared at game start var/list/managed_teams = list() /// Nebulous list of teams per round, subtracted from as teams complete their turns @@ -88,27 +92,35 @@ /obj/item/wargame_base_station/click_alt(mob/living/user) var/picked_choice = show_radial_menu(user, src, (game_phase == WARGAME_PHASE_NOTHING) ? pre_game_radial_options : mid_game_radial_options, require_near = TRUE, tooltips = TRUE) if(isnull(picked_choice)) + play_menu_sound() return CLICK_ACTION_BLOCKING switch(picked_choice) // Pregame options if(BASESTATION_JOIN_LEAVE) + play_menu_sound() try_join_leave(user) if(BASESTATION_ADD_TEAM) + play_menu_sound() create_new_team(user) if(BASESTATION_REMOVE_TEAM) + play_menu_sound() delete_team(user) if(BASESTATION_EDIT_TEAM) + play_menu_sound() edit_a_team(user) if(BASESTATION_RESET) + play_menu_sound() reset_everything(user) if(BASESTATION_START) + play_menu_sound() start_the_game(user) // Midgame options if(BASESTATION_END) + play_menu_sound() end_the_game(user) if(BASESTATION_NEXT) + play_menu_sound() advance_phase(user) - play_menu_sound() return CLICK_ACTION_SUCCESS /// Converts a given game phase number to text @@ -164,6 +176,7 @@ balloon_alert(user, "no choice made!") play_menu_sound() return + play_menu_sound() switch(option) if(JOIN_A_TEAM) var/picked_team = tgui_input_list(user, "Choose which team to join.", "Team Manager", managed_teams) diff --git a/modular_doppler/wargaming/code/conditions/_unit_conditions.dm b/modular_doppler/wargaming/code/conditions/_unit_conditions.dm index 5bd015e9743b5c..f37ec8971406ef 100644 --- a/modular_doppler/wargaming/code/conditions/_unit_conditions.dm +++ b/modular_doppler/wargaming/code/conditions/_unit_conditions.dm @@ -56,8 +56,8 @@ condition_desc = "Damage to the ship's reactor that reduces maximum action points by one." condition_lifetime_left = 3 -/datum/wargame_condition/engine_damage/applied_to_unit(datum/wargame_unit_stats/stats) +/datum/wargame_condition/reactor_damage/applied_to_unit(datum/wargame_unit_stats/stats) stats.maximum_action_points -= 1 -/datum/wargame_condition/engine_damage/removed_from_unit(datum/wargame_unit_stats/stats) +/datum/wargame_condition/reactor_damage/removed_from_unit(datum/wargame_unit_stats/stats) stats.maximum_action_points += 1 diff --git a/modular_doppler/wargaming/code/holograms/_holograms.dm b/modular_doppler/wargaming/code/holograms/_holograms.dm index 5267f6db692a80..35e56b47b89249 100644 --- a/modular_doppler/wargaming/code/holograms/_holograms.dm +++ b/modular_doppler/wargaming/code/holograms/_holograms.dm @@ -7,6 +7,7 @@ max_integrity = 30 alpha = 180 obj_flags = UNIQUE_RENAME + do_not_override_chat_colors = TRUE /// What object created this projection? Can be null as a projector isn't required for this to exist var/obj/item/wargame_projector/projector /// If this hologram ignores pixel shifting when placed, instead using swarming @@ -32,7 +33,7 @@ layer = LOW_ITEM_LAYER register_context() -/obj/item/wargame_projector/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) +/obj/structure/wargame_hologram/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) if(isnull(held_item)) context[SCREENTIP_CONTEXT_LMB] = "Open actions menu" return CONTEXTUAL_SCREENTIP_SET diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index af4f2aa6c50c7a..d5ba852da7fc07 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -145,6 +145,7 @@ /obj/item/wargame_projector/proc/create_hologram(atom/target, mob/user, list/modifiers) var/obj/structure/wargame_hologram/target_holosign = new holosign_type(get_turf(target), src) target_holosign.color = holosign_color + target_holosign.chat_color = holosign_color playsound(loc, SFX_INDUSTRIAL_SCAN, 45, TRUE) if(requires_linked_team) target_holosign.team_reference = linked_team diff --git a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm index ac6a2c794adc28..ff44e7915751b6 100644 --- a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm +++ b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm @@ -102,7 +102,7 @@ if(!target_hologram) return // ?? if(get_dist(hologram, target_hologram) <= 1) - target_hologram.unit_stats.get_attacked(user, hologram, src) + target_hologram.unit_stats.get_attacked(user, hologram, target_hologram, src) return ..() var/dir_to_target = get_dir(hologram, target_hologram) var/obj/structure/wargame_hologram/new_missile = new missile_type(get_turf(hologram), hologram.projector) diff --git a/modular_doppler/wargaming/code/unit_weapons/beams.dm b/modular_doppler/wargaming/code/unit_weapons/beams.dm index 9c576832f7f8f1..e785a807e6d915 100644 --- a/modular_doppler/wargaming/code/unit_weapons/beams.dm +++ b/modular_doppler/wargaming/code/unit_weapons/beams.dm @@ -9,7 +9,7 @@ action_point_cost = 2 /datum/wargame_weapon/anti_ship_beam/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/beam.ogg', 75, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/beam.ogg', 40, TRUE) /datum/wargame_weapon/anti_ship_beam/weapon_description() return "A high-energy beam for coring large ships with. Max range of [attack_range] tiles. [action_point_cost] AP to fire." @@ -35,7 +35,7 @@ action_point_cost = 1 /datum/wargame_weapon/pd_beam/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/beam.ogg', 75, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/beam.ogg', 40, TRUE) /datum/wargame_weapon/pd_beam/weapon_description() return "Highly focused beams with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." diff --git a/modular_doppler/wargaming/code/unit_weapons/big_guns.dm b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm index 8ff1078c789e7a..840e55397d16a5 100644 --- a/modular_doppler/wargaming/code/unit_weapons/big_guns.dm +++ b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm @@ -9,7 +9,7 @@ action_point_cost = 1 /datum/wargame_weapon/mass_driver/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 75, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 40, TRUE) /datum/wargame_weapon/mass_driver/weapon_description() return "A large mass driver for launching hardened cores at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." @@ -38,7 +38,7 @@ action_point_cost = 1 /datum/wargame_weapon/large_cannon/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 75, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 40, TRUE) /datum/wargame_weapon/large_cannon/weapon_description() return "A large calibre 7 in. cannon for launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." @@ -65,7 +65,7 @@ action_point_cost = 1 /datum/wargame_weapon/medium_cannon/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 75, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 40, TRUE) /datum/wargame_weapon/medium_cannon/weapon_description() return "A medium calibre 2.5 in. cannon fore launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." diff --git a/modular_doppler/wargaming/code/unit_weapons/bombs.dm b/modular_doppler/wargaming/code/unit_weapons/bombs.dm index ed3385b4056f8b..c3383d94a2d391 100644 --- a/modular_doppler/wargaming/code/unit_weapons/bombs.dm +++ b/modular_doppler/wargaming/code/unit_weapons/bombs.dm @@ -13,6 +13,9 @@ /datum/wargame_weapon/rockets/weapon_description() return "A barrage of unguided rockets from a fixed rack or large launcher. Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/rockets/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/swarm_launch.ogg', 40, TRUE) + /datum/wargame_weapon/rockets/weapon_firing_message(obj/firer, obj/target) firer.visible_message(span_warning("[firer] lets loose a volley of rockets at [target]!")) @@ -38,6 +41,9 @@ /datum/wargame_weapon/bombs/weapon_description() return "A group of unpowered explosive bombs to crack large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." +/datum/wargame_weapon/bombs/weapon_firing_sound(obj/firer) + playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 40, TRUE) + /datum/wargame_weapon/bombs/weapon_firing_message(obj/firer, obj/target) firer.visible_message(span_warning("[firer] makes a run at [target], releasing a large bomb that drifts toward them!")) diff --git a/modular_doppler/wargaming/code/unit_weapons/missiles.dm b/modular_doppler/wargaming/code/unit_weapons/missiles.dm index 81c131cd92ca16..2eff4c72cdf6bb 100644 --- a/modular_doppler/wargaming/code/unit_weapons/missiles.dm +++ b/modular_doppler/wargaming/code/unit_weapons/missiles.dm @@ -10,7 +10,7 @@ missile_type = /obj/structure/wargame_hologram/controllable/cruise_missile /datum/wargame_weapon/missile/cruise/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/missile_launch.ogg', 50, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/missile_launch.ogg', 30, TRUE) /datum/wargame_weapon/missile/cruise/weapon_firing_message(obj/firer, obj/target) firer.visible_message(span_warning("[firer]'s missile tubes pop open and dispense a large missile that quickly turns and burns towards [target]!")) @@ -53,7 +53,7 @@ missile_type = /obj/structure/wargame_hologram/controllable/swarm_missile /datum/wargame_weapon/missile/swarm/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/swarm_launch.ogg', 75, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/swarm_launch.ogg', 40, TRUE) /datum/wargame_weapon/missile/swarm/weapon_description() return "A swarm of small missiles for intercepting other missiles or strike craft. Becomes a controllable ship if the target is beyond short range. \ @@ -96,7 +96,7 @@ missile_type = /obj/structure/wargame_hologram/controllable/torpedo /datum/wargame_weapon/missile/torpedo/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/missile_launch.ogg', 50, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/missile_launch.ogg', 30, TRUE) /datum/wargame_weapon/missile/torpedo/weapon_description() return "A beefy torpedo for cracking large ships in two. Becomes a controllable ship if the target is beyond short range. \ diff --git a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm index 8472a8134e1a95..1c2a8efa32d5b0 100644 --- a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm +++ b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm @@ -8,7 +8,7 @@ action_point_cost = 1 /datum/wargame_weapon/autocannon/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/autocannonlong.ogg', 75, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/autocannonlong.ogg', 40, TRUE) /datum/wargame_weapon/autocannon/weapon_description() return "Low calibre 45mm autocannons for fast moving targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." @@ -34,7 +34,7 @@ action_point_cost = 1 /datum/wargame_weapon/pdc/weapon_firing_sound(obj/firer) - playsound(firer, 'modular_doppler/wargaming/sound/autocannonlong.ogg', 75, TRUE) + playsound(firer, 'modular_doppler/wargaming/sound/autocannonlong.ogg', 40, TRUE) /datum/wargame_weapon/pdc/weapon_description() return "Small calibre 26mm autocannons with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." From fd4276cf78c09c6fe4f8b0a6f3b5a3c048fd0209 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 11:50:30 -0400 Subject: [PATCH 16/36] adds even more audio feedback --- modular_doppler/wargaming/code/holograms/_holograms.dm | 1 + modular_doppler/wargaming/code/unit_stats/_unit_stats.dm | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/modular_doppler/wargaming/code/holograms/_holograms.dm b/modular_doppler/wargaming/code/holograms/_holograms.dm index 35e56b47b89249..3c15fb8aa0a8ee 100644 --- a/modular_doppler/wargaming/code/holograms/_holograms.dm +++ b/modular_doppler/wargaming/code/holograms/_holograms.dm @@ -76,6 +76,7 @@ if(!currently_our_turn()) if(prob(1/1000)) say("WAIT. YOUR. TURN!") + playsound(src, 'sound/effects/seedling_chargeup.ogg', 50) else balloon_alert(user, "not our turn!") return diff --git a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm index 6926c31db76f14..802f527c743fae 100644 --- a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -93,7 +93,8 @@ "Punch out! Punch out! We're had!", ) hologram.say(pick(lines)) - playsound(hologram, 'modular_doppler/wargaming/sound/ship_explode.ogg', 50, TRUE) + playsound(hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) + playsound(hologram, 'modular_doppler/wargaming/sound/ship_explode.ogg', 50, TRUE) do_sparks(3, FALSE, hologram) qdel(hologram) @@ -136,6 +137,7 @@ return if(weapon_choice.firing_voiceline() && talkative) hologram.say(weapon_choice.firing_voiceline()) + playsound(hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) weapon_choice.weapon_firing_message(hologram, target_hologram) weapon_choice.weapon_firing_sound(hologram) if(!weapon_choice.all_special_effects) @@ -155,11 +157,13 @@ if(incoming_attack_roll <= total_armor_class) if(attacking_hologram.unit_stats.talkative) attacking_hologram.say(missed_voiceline()) + playsound(attacking_hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) hologram.visible_message(span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was less than or equal to the target's effective armor class, [total_armor_class].")) return FALSE if((incoming_attack_roll + weapon_used.damage_roll_bonus) <= armor_class) if(attacking_hologram.unit_stats.talkative) attacking_hologram.say(nonpen_voiceline()) + playsound(attacking_hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) hologram.visible_message(span_warning("Attacker's damage roll, [incoming_attack_roll + weapon_used.damage_roll_bonus], was less than or equal to the target's armor class, [armor_class].")) playsound(hologram, 'modular_doppler/wargaming/sound/ship_hit.ogg', 50, TRUE) return TRUE // If our weapon is weak (PDC) then it makes our attack roll less than armor class @@ -169,6 +173,7 @@ current_conditions += new_condition if(attacking_hologram.unit_stats.talkative) attacking_hologram.say(good_hit_voiceline()) + playsound(attacking_hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) hologram.visible_message(span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was higher than the target's effective armor class, [total_armor_class].")) hologram.Shake(2, 0, 2 SECONDS) playsound(hologram, 'modular_doppler/wargaming/sound/ship_hit.ogg', 50, TRUE) @@ -237,6 +242,7 @@ /// Any effects to do post movement, like voicelines or sound effects /datum/wargame_unit_stats/proc/post_move_effects(obj/hologram) if(prob(50)) + playsound(hologram, 'sound/items/radio/radio_talk.ogg', 50, TRUE) return var/static/list/lines = list( "Moving to designated coordinates.", @@ -246,6 +252,7 @@ "Starting burn.", ) hologram.say("[pick(lines)]") + playsound(hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) /datum/wargame_unit_stats/generic unit_class = "Generic Thing" From dd3dbd2380fd9e1f88edcd3a72bca0aa2fa213a3 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 12:05:51 -0400 Subject: [PATCH 17/36] thing thing has no games --- modular_doppler/wargaming/code/base_station.dm | 3 ++- modular_doppler/wargaming/code/team_datums.dm | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index 706cadf82b024f..85751b838ba7bd 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -62,7 +62,8 @@ name = "\improper DopplerStation 2" desc = "A base station for holographic wargames, all controllers and interactive wearables link to this machine and are automatically managed by it. \ This looks like the special pre-order edition of the console, how did one of these get out this far? The faceplate has been replaced with one \ - plated in gold, and someone seems to have left a sticker of a little dolphin on the case." + plated in gold, and someone seems to have left a sticker of a little dolphin on the case. \ + Whoever owned this before must lament their purchase every day, this thing only has one game on it!" icon_state = "basestation_gold" update_appearance(UPDATE_ICON) diff --git a/modular_doppler/wargaming/code/team_datums.dm b/modular_doppler/wargaming/code/team_datums.dm index c68b7da69075db..fe6252882ca000 100644 --- a/modular_doppler/wargaming/code/team_datums.dm +++ b/modular_doppler/wargaming/code/team_datums.dm @@ -16,7 +16,7 @@ if(hologram.controllable) hologram.unit_stats.make_ready() -/// Tells every controllable hologram to update it's conditions and explode if necessary +/// Tells every controllable hologram to update its conditions and explode if necessary /datum/wargaming_team/proc/update_all_units() for(var/obj/item/wargame_projector/projector as anything in tracked_projectors) for(var/obj/structure/wargame_hologram/hologram as anything in projector.projections) From db9863543e03ef8fbf60d77d191b1758d0b02aa2 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 12:07:12 -0400 Subject: [PATCH 18/36] belt --- modular_doppler/wargaming/code/base_station.dm | 1 + modular_doppler/wargaming/code/projectors.dm | 1 + 2 files changed, 2 insertions(+) diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index 85751b838ba7bd..3db9c02f996254 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -13,6 +13,7 @@ drop_sound = 'sound/items/handling/tools/rcd_drop.ogg' pickup_sound = 'sound/items/handling/tools/rcd_pickup.ogg' do_not_override_chat_colors = TRUE + slot_flags = ITEM_SLOT_BELT chat_color = LIGHT_COLOR_COPPER_OXIDE /// List of all team datums this controller is currently managing, empty teams are cleared at game start var/list/managed_teams = list() diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index d5ba852da7fc07..c27bede581984f 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -9,6 +9,7 @@ inhand_icon_state = "generic" worn_icon = 'modular_doppler/wargaming/icons/mob/worn.dmi' worn_icon_state = "generic" + slot_flags = ITEM_SLOT_BELT w_class = WEIGHT_CLASS_SMALL item_flags = NOBLUDGEON pickup_sound = SFX_GENERIC_DEVICE_PICKUP From c51688150dfc303b3095ba0f7a23667d2927e2ef Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 12:42:44 -0400 Subject: [PATCH 19/36] final fixes --- .../wargaming/code/base_station.dm | 3 +-- .../wargaming/code/holograms/_holograms.dm | 4 +++- modular_doppler/wargaming/code/projectors.dm | 19 +++++++++++-------- .../wargaming/code/unit_stats/_unit_stats.dm | 3 ++- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index 3db9c02f996254..9d7285b3493a6d 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -54,8 +54,7 @@ make_special_edition() /obj/item/wargame_base_station/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) - if(isnull(held_item)) - context[SCREENTIP_CONTEXT_ALT_LMB] = "Open console menu" + context[SCREENTIP_CONTEXT_ALT_LMB] = "Open console menu" return CONTEXTUAL_SCREENTIP_SET /// Turns the console gold and gives it a silly name diff --git a/modular_doppler/wargaming/code/holograms/_holograms.dm b/modular_doppler/wargaming/code/holograms/_holograms.dm index 3c15fb8aa0a8ee..11b3cb9d4ad1bc 100644 --- a/modular_doppler/wargaming/code/holograms/_holograms.dm +++ b/modular_doppler/wargaming/code/holograms/_holograms.dm @@ -34,7 +34,9 @@ register_context() /obj/structure/wargame_hologram/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) - if(isnull(held_item)) + if(istype(held_item, /obj/item/wargame_projector)) + context[SCREENTIP_CONTEXT_LMB] = "Delete hologram" + else context[SCREENTIP_CONTEXT_LMB] = "Open actions menu" return CONTEXTUAL_SCREENTIP_SET diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index c27bede581984f..334f0e912b7283 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -56,11 +56,10 @@ register_context() /obj/item/wargame_projector/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) - if(isnull(held_item)) - context[SCREENTIP_CONTEXT_LMB] = "Change hologram" - if(!requires_linked_team) - context[SCREENTIP_CONTEXT_ALT_LMB] = "Change hologram color" - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Clear all holograms" + context[SCREENTIP_CONTEXT_LMB] = "Change hologram" + if(!requires_linked_team) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Change hologram color" + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Clear all holograms" return CONTEXTUAL_SCREENTIP_SET /obj/item/wargame_projector/update_appearance() @@ -72,10 +71,13 @@ /obj/item/wargame_projector/examine(mob/user) . = ..() - . += span_notice("Use the projector in hand to change what type of hologram it creates.") + if(!isnull(our_team)) + . += span_notice("This projector is linked to the [our_team.team_name] team!") + . += "Use the projector [EXAMINE_HINT("in hand")] to change what type of hologram it creates." + var/datum/wargaming_team/our_team = linked_team?.resolve() if(!requires_linked_team) - . += span_notice("Alt clicking the projector will let you change the color of the next hologram it makes.") - . += span_warning("Control clicking the projector will allow you to clear all active holograms.") + . += "[EXAMINE_HINT("Alt-click")] will let you change the color of the next hologram it makes." + . += span_warning("[EXAMINE_HINT("Ctrl-Click")] will allow you to clear all active holograms.") /// Plays a clicking sound for menu actions /obj/item/wargame_projector/proc/play_menu_sound() @@ -89,6 +91,7 @@ /// Changes the selected hologram to one of the options from the hologram list /obj/item/wargame_projector/proc/select_hologram(mob/user) + play_menu_sound() var/picked_choice = show_radial_menu( user, src, diff --git a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm index 802f527c743fae..7937a7680c57e1 100644 --- a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -251,7 +251,8 @@ "Getting there.", "Starting burn.", ) - hologram.say("[pick(lines)]") + if(talkative) + hologram.say("[pick(lines)]") playsound(hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) /datum/wargame_unit_stats/generic From 3501a6048d2c316bf4e0d2811757f9c5aab63c82 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 12:47:44 -0400 Subject: [PATCH 20/36] o kurwa --- modular_doppler/wargaming/code/projectors.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index 334f0e912b7283..b17a412252a52f 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -71,10 +71,10 @@ /obj/item/wargame_projector/examine(mob/user) . = ..() + var/datum/wargaming_team/our_team = linked_team?.resolve() if(!isnull(our_team)) . += span_notice("This projector is linked to the [our_team.team_name] team!") . += "Use the projector [EXAMINE_HINT("in hand")] to change what type of hologram it creates." - var/datum/wargaming_team/our_team = linked_team?.resolve() if(!requires_linked_team) . += "[EXAMINE_HINT("Alt-click")] will let you change the color of the next hologram it makes." . += span_warning("[EXAMINE_HINT("Ctrl-Click")] will allow you to clear all active holograms.") From 599c2c187cb5207f461f2b227ec56c773281ec92 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 14:29:25 -0400 Subject: [PATCH 21/36] fixes everything --- _maps/templates/holodeck_doppler_wargame_board.dmm | 2 +- modular_doppler/wargaming/code/holodeck.dm | 2 +- modular_doppler/wargaming/code/unit_stats/cruiser.dm | 3 ++- modular_doppler/wargaming/code/unit_stats/strike.dm | 4 ++-- modular_doppler/wargaming/code/unit_weapons/small_guns.dm | 4 ++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/_maps/templates/holodeck_doppler_wargame_board.dmm b/_maps/templates/holodeck_doppler_wargame_board.dmm index 0e6e76a81c322d..75f23a69fd1229 100644 --- a/_maps/templates/holodeck_doppler_wargame_board.dmm +++ b/_maps/templates/holodeck_doppler_wargame_board.dmm @@ -260,7 +260,7 @@ }, /area/template_noop) "F" = ( -/turf/open/floor/holofloor/wargame, +/turf/open/indestructible/wargame, /area/template_noop) "I" = ( /obj/effect/turf_decal/arrows/white{ diff --git a/modular_doppler/wargaming/code/holodeck.dm b/modular_doppler/wargaming/code/holodeck.dm index d6e00b674aef61..edaa4dd5e90ecb 100644 --- a/modular_doppler/wargaming/code/holodeck.dm +++ b/modular_doppler/wargaming/code/holodeck.dm @@ -1,4 +1,4 @@ -/turf/open/floor/holofloor/wargame +/turf/open/indestructible/wargame icon = 'modular_doppler/wargaming/icons/turf.dmi' icon_state = "holodeck" diff --git a/modular_doppler/wargaming/code/unit_stats/cruiser.dm b/modular_doppler/wargaming/code/unit_stats/cruiser.dm index 625b52b030c14b..a0f1ac8f76876a 100644 --- a/modular_doppler/wargaming/code/unit_stats/cruiser.dm +++ b/modular_doppler/wargaming/code/unit_stats/cruiser.dm @@ -1,7 +1,7 @@ /datum/wargame_unit_stats/cruiser abstract_type = /datum/wargame_unit_stats/cruiser generates_name = TRUE - conditions_limit = 6 + conditions_limit = 4 possible_conditions = list( /datum/wargame_condition/hull_damage, /datum/wargame_condition/engine_damage, @@ -49,6 +49,7 @@ evasion_modifier = 0 movement_cost = 2 maximum_action_points = 4 + conditions_limit = 5 weaponry = list( /datum/wargame_weapon/missile/swarm, /datum/wargame_weapon/missile/torpedo, diff --git a/modular_doppler/wargaming/code/unit_stats/strike.dm b/modular_doppler/wargaming/code/unit_stats/strike.dm index d7ea944f3fc2a7..d9c0607935951c 100644 --- a/modular_doppler/wargaming/code/unit_stats/strike.dm +++ b/modular_doppler/wargaming/code/unit_stats/strike.dm @@ -20,7 +20,7 @@ armor_class = 7 evasion_modifier = 4 movement_cost = 1 - maximum_action_points = 5 + maximum_action_points = 3 weaponry = list( /datum/wargame_weapon/pdc, /datum/wargame_weapon/rockets, @@ -34,7 +34,7 @@ armor_class = 6 evasion_modifier = 5 movement_cost = 1 - maximum_action_points = 5 + maximum_action_points = 3 weaponry = list( /datum/wargame_weapon/pdc, /datum/wargame_weapon/autocannon, diff --git a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm index 1c2a8efa32d5b0..18f795fc7bd5f9 100644 --- a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm +++ b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm @@ -26,8 +26,8 @@ /datum/wargame_weapon/pdc weapon_name = "26mm PDC" - attack_roll = "1d12+8" - damage_roll_bonus = -5 + attack_roll = "1d12+6" + damage_roll_bonus = -3 attack_range = 1 evadable = FALSE radial_icon_state = "weapon_guns_pdc" From 4574a6254c6435f126f8db164c8c26260d920063 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 14:33:49 -0400 Subject: [PATCH 22/36] fixes conditions not clearing properly --- modular_doppler/wargaming/code/unit_stats/_unit_stats.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm index 7937a7680c57e1..66c20def2aa789 100644 --- a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -74,7 +74,7 @@ for(var/datum/wargame_condition/condition as anything in current_conditions) condition.condition_lifetime_left-- if(condition.condition_lifetime_left <= 0) - condition.removed_from_unit() + condition.removed_from_unit(src) current_conditions -= condition qdel(condition) if(length(current_conditions) > conditions_limit) From 324842fe54e441c8c37660f62d37f80bd036942e Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 14:51:49 -0400 Subject: [PATCH 23/36] makes the floors holodeck compatible --- modular_doppler/wargaming/code/holodeck.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/modular_doppler/wargaming/code/holodeck.dm b/modular_doppler/wargaming/code/holodeck.dm index edaa4dd5e90ecb..25b92cce7338ea 100644 --- a/modular_doppler/wargaming/code/holodeck.dm +++ b/modular_doppler/wargaming/code/holodeck.dm @@ -1,6 +1,7 @@ /turf/open/indestructible/wargame icon = 'modular_doppler/wargaming/icons/turf.dmi' icon_state = "holodeck" + holodeck_compatible = TRUE /datum/map_template/holodeck/doppler_wargame_board name = "Holodeck - Wargame Simulator" From 22928a0670764863cb30eabbe86ffd9e57f53223 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 16:20:59 -0400 Subject: [PATCH 24/36] fixes the holodeck for real --- code/modules/holodeck/computer.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm index 11c006047f2da4..8f348390679152 100644 --- a/code/modules/holodeck/computer.dm +++ b/code/modules/holodeck/computer.dm @@ -27,7 +27,7 @@ and clear when youre done! if you dont i will use :newspaper2: on you /// typecache for turfs that should be considered ok during floorchecks. /// A linked turf being anything not in this typecache will cause the holodeck to perform an emergency shutdown. -GLOBAL_LIST_INIT(typecache_holodeck_linked_floorcheck_ok, typecacheof(list(/turf/open/floor/holofloor, /turf/closed))) +GLOBAL_LIST_INIT(typecache_holodeck_linked_floorcheck_ok, typecacheof(list(/turf/open/floor/holofloor, /turf/closed, /turf/open/indestructible/wargame))) // DOPPLER EDIT - Wargame holodeck - ORIGINAL: GLOBAL_LIST_INIT(typecache_holodeck_linked_floorcheck_ok, typecacheof(list(/turf/open/floor/holofloor, /turf/closed))) /obj/machinery/computer/holodeck name = "holodeck control console" From 4c36cb31146e2ddcfb3d209fec34d96225433a83 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 17:42:58 -0400 Subject: [PATCH 25/36] can't believe we're balance patching already --- modular_doppler/wargaming/code/projectors.dm | 2 + .../wargaming/code/unit_stats/_unit_stats.dm | 19 +++++- .../wargaming/code/unit_stats/asteroid.dm | 4 ++ .../wargaming/code/unit_stats/cruiser.dm | 1 - .../wargaming/code/unit_stats/missiles.dm | 4 ++ .../wargaming/code/unit_stats/strike.dm | 7 ++- .../code/unit_weapons/_unit_weapons.dm | 2 +- .../wargaming/code/unit_weapons/beams.dm | 2 +- .../wargaming/code/unit_weapons/missiles.dm | 3 + strings/~doppler/wargame_identifiers.json | 58 +++++++++++++++++++ 10 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 strings/~doppler/wargame_identifiers.json diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index b17a412252a52f..3ea86be92d50a7 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -196,6 +196,8 @@ qdel(interacting_with) play_menu_sound() return ITEM_INTERACT_SUCCESS + if(!isturf(interacting_with)) + return NONE if(!check_can_place_hologram(interacting_with, user)) play_menu_sound() return NONE diff --git a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm index 66c20def2aa789..fc5bc0aa73d514 100644 --- a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -45,12 +45,16 @@ if(isnull(unit_class) && isnull(unit_name)) return if(generates_name) - unit_name = pick_list_replacements("~doppler/salvage_shuttle.json", "ship_name") + unit_name = create_unit_name() if(isnull(unit_name)) hologram.name = unit_class else hologram.name = "[unit_class] - ([unit_name])" +/// Generates a name for the unit, overwriten by subtypes for different naming schemes +/datum/wargame_unit_stats/proc/create_unit_name() + return pick_list_replacements("~doppler/salvage_shuttle.json", "ship_name") + /// Sets up weapon datums and radial options /datum/wargame_unit_stats/proc/set_up_weaponry() if(!length(weaponry)) @@ -71,14 +75,27 @@ /// Runs through everything we might need to process during the effects phase /datum/wargame_unit_stats/proc/effects_phase_process(obj/structure/wargame_hologram/hologram) + var/repaired_this_phase = FALSE for(var/datum/wargame_condition/condition as anything in current_conditions) condition.condition_lifetime_left-- if(condition.condition_lifetime_left <= 0) condition.removed_from_unit(src) current_conditions -= condition + repaired_this_phase = TRUE qdel(condition) if(length(current_conditions) > conditions_limit) im_boutta_blow(hologram) + return + if(repaired_this_phase) + var/static/list/lines = list( + "Conditions repaired, combat functionality restored.", + "Repairs complete, let's get back in the fight!", + "Damage control reports successful repairs.", + "One less hole in the wall, let's keep at it!", + "Integrity partially restored, let's show them we're still in this!", + ) + hologram.say(pick(lines)) + playsound(hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) /// What to do when this unit explodes, good place to spawn a replacement "wreck" unit type /datum/wargame_unit_stats/proc/im_boutta_blow(obj/structure/wargame_hologram/hologram) diff --git a/modular_doppler/wargaming/code/unit_stats/asteroid.dm b/modular_doppler/wargaming/code/unit_stats/asteroid.dm index 837e1b4ec8e2ab..bc6c79e6c0698d 100644 --- a/modular_doppler/wargaming/code/unit_stats/asteroid.dm +++ b/modular_doppler/wargaming/code/unit_stats/asteroid.dm @@ -18,12 +18,16 @@ /datum/wargame_unit_stats/terrain/asteroid unit_class = "asteroid" unit_description = "A large asteroid that serves as good cover. Can be broken apart with sufficient damage." + generates_name = TRUE armor_class = 13 evasion_modifier = 0 movement_cost = 100 maximum_action_points = 0 weaponry = list() +/datum/wargame_unit_stats/terrain/asteroid/create_unit_name() + return "[pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] [rand(100, 999)]" + /datum/wargame_unit_stats/terrain/dust_cloud unit_class = "dust cloud" unit_description = "A large cloud of space dust of some kind. While unserviceable as cover, gives a boost to evasion while inside of it." diff --git a/modular_doppler/wargaming/code/unit_stats/cruiser.dm b/modular_doppler/wargaming/code/unit_stats/cruiser.dm index a0f1ac8f76876a..c02e719116b1b6 100644 --- a/modular_doppler/wargaming/code/unit_stats/cruiser.dm +++ b/modular_doppler/wargaming/code/unit_stats/cruiser.dm @@ -49,7 +49,6 @@ evasion_modifier = 0 movement_cost = 2 maximum_action_points = 4 - conditions_limit = 5 weaponry = list( /datum/wargame_weapon/missile/swarm, /datum/wargame_weapon/missile/torpedo, diff --git a/modular_doppler/wargaming/code/unit_stats/missiles.dm b/modular_doppler/wargaming/code/unit_stats/missiles.dm index 656c0253e3c844..015cdb38e0493a 100644 --- a/modular_doppler/wargaming/code/unit_stats/missiles.dm +++ b/modular_doppler/wargaming/code/unit_stats/missiles.dm @@ -1,6 +1,10 @@ /datum/wargame_unit_stats/missile abstract_type = /datum/wargame_unit_stats/missile talkative = FALSE + generates_name = TRUE + +/datum/wargame_unit_stats/missile/create_unit_name() + return "Track [pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] [rand(1,99)]" /datum/wargame_unit_stats/missile/im_boutta_blow(obj/structure/wargame_hologram/hologram) hologram.visible_message(span_warning("[hologram] is reduced to data with a bright spark!")) diff --git a/modular_doppler/wargaming/code/unit_stats/strike.dm b/modular_doppler/wargaming/code/unit_stats/strike.dm index d9c0607935951c..3efec784bfc8f5 100644 --- a/modular_doppler/wargaming/code/unit_stats/strike.dm +++ b/modular_doppler/wargaming/code/unit_stats/strike.dm @@ -1,6 +1,6 @@ /datum/wargame_unit_stats/strike abstract_type = /datum/wargame_unit_stats/strike - generates_name = FALSE + generates_name = TRUE conditions_limit = 2 possible_conditions = list( /datum/wargame_condition/hull_damage, @@ -14,6 +14,9 @@ hologram.visible_message(span_warning("[hologram] starts to careen off course before crackling and exploding!")) return ..() +/datum/wargame_unit_stats/strike/create_unit_name() + return "[pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] Squadrom" + /datum/wargame_unit_stats/strike/wing unit_class = "strike wing" unit_description = "A strike wing of bombers and its fighter escorts." @@ -38,6 +41,6 @@ weaponry = list( /datum/wargame_weapon/pdc, /datum/wargame_weapon/autocannon, - /datum/wargame_weapon/missile/swarm, + /datum/wargame_weapon/missile/swarm/strike, /datum/wargame_weapon/ramming/strike, ) diff --git a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm index ff44e7915751b6..e9319643f57658 100644 --- a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm +++ b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm @@ -41,7 +41,7 @@ /datum/wargame_weapon/proc/pick_target(mob/living/user, obj/structure/wargame_hologram/hologram) var/datum/wargaming_team/hologram_team = hologram.team_reference?.resolve() var/list/potential_targets = list() - for(var/obj/structure/wargame_hologram/other_hologram in view(attack_range, hologram)) + for(var/obj/structure/wargame_hologram/other_hologram in view(attack_range, get_turf(hologram))) if(!other_hologram.unit_stats.can_be_a_target) continue if(isnull(hologram_team)) diff --git a/modular_doppler/wargaming/code/unit_weapons/beams.dm b/modular_doppler/wargaming/code/unit_weapons/beams.dm index e785a807e6d915..0e34ba5cdc943b 100644 --- a/modular_doppler/wargaming/code/unit_weapons/beams.dm +++ b/modular_doppler/wargaming/code/unit_weapons/beams.dm @@ -6,7 +6,7 @@ evadable = FALSE small_ship_disadvantage = TRUE radial_icon_state = "weapon_beam" - action_point_cost = 2 + action_point_cost = 1 /datum/wargame_weapon/anti_ship_beam/weapon_firing_sound(obj/firer) playsound(firer, 'modular_doppler/wargaming/sound/beam.ogg', 40, TRUE) diff --git a/modular_doppler/wargaming/code/unit_weapons/missiles.dm b/modular_doppler/wargaming/code/unit_weapons/missiles.dm index 2eff4c72cdf6bb..48ea82f6261f1a 100644 --- a/modular_doppler/wargaming/code/unit_weapons/missiles.dm +++ b/modular_doppler/wargaming/code/unit_weapons/missiles.dm @@ -52,6 +52,9 @@ maximum_ammo = 12 missile_type = /obj/structure/wargame_hologram/controllable/swarm_missile +/datum/wargame_weapon/missile/swarm/strike + maximum_ammo = 4 + /datum/wargame_weapon/missile/swarm/weapon_firing_sound(obj/firer) playsound(firer, 'modular_doppler/wargaming/sound/swarm_launch.ogg', 40, TRUE) diff --git a/strings/~doppler/wargame_identifiers.json b/strings/~doppler/wargame_identifiers.json new file mode 100644 index 00000000000000..314e43b280f35e --- /dev/null +++ b/strings/~doppler/wargame_identifiers.json @@ -0,0 +1,58 @@ +{ + "name_word": [ + "Ceres", + "Vesta", + "Pallas", + "Hygiea", + "Europa", + "Davida", + "Sylvia", + "Eunomia", + "Juno", + "Psyche", + "Thisbe", + "Doris", + "Fortuna", + "Themis", + "Alfa", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliett", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X-Ray", + "Yankee", + "Zulu", + "Able", + "Baker", + "Easy", + "Fox", + "Jig", + "King", + "Love", + "Queen", + "Roger", + "Sail", + "Sugar", + "Uncle", + "Yoke", + "Zebra" + ] +} From e935494fe6f51116e71237a1289c2e25f319fb9c Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 17:48:38 -0400 Subject: [PATCH 26/36] makes some conditions last a little longer --- modular_doppler/wargaming/code/conditions/_unit_conditions.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modular_doppler/wargaming/code/conditions/_unit_conditions.dm b/modular_doppler/wargaming/code/conditions/_unit_conditions.dm index f37ec8971406ef..5df12f6a6f47bf 100644 --- a/modular_doppler/wargaming/code/conditions/_unit_conditions.dm +++ b/modular_doppler/wargaming/code/conditions/_unit_conditions.dm @@ -43,7 +43,7 @@ /datum/wargame_condition/engine_damage condition_name = "Engine Damage" condition_desc = "Damage to the engines that makes movement cost an extra action point." - condition_lifetime_left = 2 + condition_lifetime_left = 3 /datum/wargame_condition/engine_damage/applied_to_unit(datum/wargame_unit_stats/stats) stats.movement_cost += 1 @@ -54,7 +54,7 @@ /datum/wargame_condition/reactor_damage condition_name = "Reactor Damage" condition_desc = "Damage to the ship's reactor that reduces maximum action points by one." - condition_lifetime_left = 3 + condition_lifetime_left = 4 /datum/wargame_condition/reactor_damage/applied_to_unit(datum/wargame_unit_stats/stats) stats.maximum_action_points -= 1 From c59d4fdf2389afacf7ca8a578cb7d7fc709e5dff Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 17:53:21 -0400 Subject: [PATCH 27/36] makes missiles always explode on attack missed or not --- modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm index e9319643f57658..ef5b9f1a34b08e 100644 --- a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm +++ b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm @@ -87,8 +87,7 @@ radial_icon_state = "weapon_ram" /datum/wargame_weapon/ramming/special_effects_fire(mob/living/user, datum/wargame_unit_stats/stats, obj/structure/wargame_hologram/hologram, obj/structure/wargame_hologram/target_hologram, impact) - if(impact) - stats.im_boutta_blow(hologram) + stats.im_boutta_blow(hologram) // Generic missile launch weapon /datum/wargame_weapon/missile From 6ce0ed64b3c47e93d0e9d6848f6a4a4be1302ca0 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 20:32:32 -0400 Subject: [PATCH 28/36] fixes hologram emissives --- modular_doppler/wargaming/code/projectors.dm | 1 + modular_doppler/wargaming/code/unit_stats/missiles.dm | 2 +- modular_doppler/wargaming/code/unit_weapons/beams.dm | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index 3ea86be92d50a7..32cb660c0600a8 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -150,6 +150,7 @@ var/obj/structure/wargame_hologram/target_holosign = new holosign_type(get_turf(target), src) target_holosign.color = holosign_color target_holosign.chat_color = holosign_color + target_holosign.update_appearance(UPDATE_OVERLAYS) playsound(loc, SFX_INDUSTRIAL_SCAN, 45, TRUE) if(requires_linked_team) target_holosign.team_reference = linked_team diff --git a/modular_doppler/wargaming/code/unit_stats/missiles.dm b/modular_doppler/wargaming/code/unit_stats/missiles.dm index 015cdb38e0493a..19c497ab489042 100644 --- a/modular_doppler/wargaming/code/unit_stats/missiles.dm +++ b/modular_doppler/wargaming/code/unit_stats/missiles.dm @@ -20,7 +20,7 @@ armor_class = 4 evasion_modifier = 1 movement_cost = 1 - maximum_action_points = 2 + maximum_action_points = 3 is_small_vessel = TRUE unit_size = WARGAME_SIZE_SMALL weaponry = list( diff --git a/modular_doppler/wargaming/code/unit_weapons/beams.dm b/modular_doppler/wargaming/code/unit_weapons/beams.dm index 0e34ba5cdc943b..a72ff1fa982667 100644 --- a/modular_doppler/wargaming/code/unit_weapons/beams.dm +++ b/modular_doppler/wargaming/code/unit_weapons/beams.dm @@ -1,6 +1,6 @@ /datum/wargame_weapon/anti_ship_beam weapon_name = "Anti-Ship Beam" - attack_roll = "1d20+5" + attack_roll = "2d12+7" damage_roll_bonus = 0 attack_range = 1 evadable = FALSE From ab223fb4c703c3853475b5d694dd90dfe54b7903 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 23:09:40 -0400 Subject: [PATCH 29/36] makes the game more accessible to disabilities --- .../wargaming/code/base_station.dm | 4 ++++ modular_doppler/wargaming/code/projectors.dm | 8 ++++---- .../wargaming/code/unit_stats/_unit_stats.dm | 9 ++++++--- .../wargaming/code/unit_stats/asteroid.dm | 3 ++- .../wargaming/code/unit_stats/corvette.dm | 3 ++- .../wargaming/code/unit_stats/cruiser.dm | 3 ++- .../wargaming/code/unit_stats/destroyer.dm | 3 ++- .../wargaming/code/unit_stats/missiles.dm | 3 ++- .../wargaming/code/unit_stats/static.dm | 3 ++- .../wargaming/code/unit_stats/strike.dm | 5 +++-- .../code/unit_weapons/_unit_weapons.dm | 13 +++++++------ .../wargaming/code/unit_weapons/beams.dm | 6 ++++-- .../wargaming/code/unit_weapons/big_guns.dm | 9 ++++++--- .../wargaming/code/unit_weapons/bombs.dm | 6 ++++-- .../wargaming/code/unit_weapons/missiles.dm | 18 ++++++++++++------ .../wargaming/code/unit_weapons/ramming.dm | 3 ++- .../wargaming/code/unit_weapons/small_guns.dm | 6 ++++-- modular_doppler/wargaming/icons/items.dmi | Bin 1214 -> 1432 bytes 18 files changed, 68 insertions(+), 37 deletions(-) diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index 9d7285b3493a6d..ce73d03d247083 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -57,6 +57,10 @@ context[SCREENTIP_CONTEXT_ALT_LMB] = "Open console menu" return CONTEXTUAL_SCREENTIP_SET +/obj/item/wargame_base_station/update_overlays() + . = ..() + . += emissive_appearance(icon, "basestation_emissive", src, alpha = 100) + /// Turns the console gold and gives it a silly name /obj/item/wargame_base_station/proc/make_special_edition() name = "\improper DopplerStation 2" diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index 32cb660c0600a8..830721dc17d661 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -62,12 +62,12 @@ context[SCREENTIP_CONTEXT_CTRL_LMB] = "Clear all holograms" return CONTEXTUAL_SCREENTIP_SET -/obj/item/wargame_projector/update_appearance() +/obj/item/wargame_projector/update_overlays() . = ..() - cut_overlays() var/image/color_select_overlay = image(icon = icon, icon_state = "[base_icon_state]_screen") color_select_overlay.color = holosign_color - add_overlay(color_select_overlay) + . += color_select_overlay + . += emissive_appearance(icon, "[base_icon_state]_screen", src) /obj/item/wargame_projector/examine(mob/user) . = ..() @@ -198,7 +198,7 @@ play_menu_sound() return ITEM_INTERACT_SUCCESS if(!isturf(interacting_with)) - return NONE + return ..() if(!check_can_place_hologram(interacting_with, user)) play_menu_sound() return NONE diff --git a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm index fc5bc0aa73d514..4ffa3cbdcf040e 100644 --- a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -175,13 +175,15 @@ if(attacking_hologram.unit_stats.talkative) attacking_hologram.say(missed_voiceline()) playsound(attacking_hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) - hologram.visible_message(span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was less than or equal to the target's effective armor class, [total_armor_class].")) + hologram.visible_message(span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was less than or equal to the target's effective armor class, [total_armor_class]."), \ + blind_message = span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was less than or equal to the target's effective armor class, [total_armor_class].")) return FALSE if((incoming_attack_roll + weapon_used.damage_roll_bonus) <= armor_class) if(attacking_hologram.unit_stats.talkative) attacking_hologram.say(nonpen_voiceline()) playsound(attacking_hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) - hologram.visible_message(span_warning("Attacker's damage roll, [incoming_attack_roll + weapon_used.damage_roll_bonus], was less than or equal to the target's armor class, [armor_class].")) + hologram.visible_message(span_warning("Attacker's damage roll, [incoming_attack_roll + weapon_used.damage_roll_bonus], was less than or equal to the target's armor class, [armor_class]."), \ + blind_message = span_warning("Attacker's damage roll, [incoming_attack_roll + weapon_used.damage_roll_bonus], was less than or equal to the target's armor class, [armor_class].")) playsound(hologram, 'modular_doppler/wargaming/sound/ship_hit.ogg', 50, TRUE) return TRUE // If our weapon is weak (PDC) then it makes our attack roll less than armor class var/datum/wargame_condition/new_condition = pick(possible_conditions) @@ -191,7 +193,8 @@ if(attacking_hologram.unit_stats.talkative) attacking_hologram.say(good_hit_voiceline()) playsound(attacking_hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) - hologram.visible_message(span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was higher than the target's effective armor class, [total_armor_class].")) + hologram.visible_message(span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was higher than the target's effective armor class, [total_armor_class]."), \ + blind_message = span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was higher than the target's effective armor class, [total_armor_class].")) hologram.Shake(2, 0, 2 SECONDS) playsound(hologram, 'modular_doppler/wargaming/sound/ship_hit.ogg', 50, TRUE) return TRUE diff --git a/modular_doppler/wargaming/code/unit_stats/asteroid.dm b/modular_doppler/wargaming/code/unit_stats/asteroid.dm index bc6c79e6c0698d..b4696ff01626fb 100644 --- a/modular_doppler/wargaming/code/unit_stats/asteroid.dm +++ b/modular_doppler/wargaming/code/unit_stats/asteroid.dm @@ -12,7 +12,8 @@ return /datum/wargame_unit_stats/terrain/im_boutta_blow(obj/structure/wargame_hologram/hologram) - hologram.visible_message(span_warning("[hologram] cracks into pieces before fading into a digital cloud!")) + hologram.visible_message(span_warning("[hologram] cracks into pieces before fading into a digital cloud!"), \ + blind_message = span_warning("[hologram] cracks into pieces before fading into a digital cloud!")) return ..() /datum/wargame_unit_stats/terrain/asteroid diff --git a/modular_doppler/wargaming/code/unit_stats/corvette.dm b/modular_doppler/wargaming/code/unit_stats/corvette.dm index 8d56917e8e0350..7b21c8909c3413 100644 --- a/modular_doppler/wargaming/code/unit_stats/corvette.dm +++ b/modular_doppler/wargaming/code/unit_stats/corvette.dm @@ -11,7 +11,8 @@ unit_size = WARGAME_SIZE_SMALL /datum/wargame_unit_stats/corvette/im_boutta_blow(obj/structure/wargame_hologram/hologram) - hologram.visible_message(span_warning("[hologram] flashes and disappears as its reactor goes critical!")) + hologram.visible_message(span_warning("[hologram] flashes and disappears as its reactor goes critical!"), \ + blind_message = span_warning("[hologram] flashes and disappears as its reactor goes critical!")) return ..() /datum/wargame_unit_stats/corvette/picket diff --git a/modular_doppler/wargaming/code/unit_stats/cruiser.dm b/modular_doppler/wargaming/code/unit_stats/cruiser.dm index c02e719116b1b6..44469b73fefab2 100644 --- a/modular_doppler/wargaming/code/unit_stats/cruiser.dm +++ b/modular_doppler/wargaming/code/unit_stats/cruiser.dm @@ -11,7 +11,8 @@ unit_size = WARGAME_SIZE_LARGE /datum/wargame_unit_stats/cruiser/im_boutta_blow(obj/structure/wargame_hologram/hologram) - hologram.visible_message(span_warning("[hologram] starts to drift off course before shattering into a large debris field!")) + hologram.visible_message(span_warning("[hologram] starts to drift off course before shattering into a large debris field!"), \ + blind_message = span_warning("[hologram] starts to drift off course before shattering into a large debris field!")) return ..() /datum/wargame_unit_stats/cruiser/mass_driver diff --git a/modular_doppler/wargaming/code/unit_stats/destroyer.dm b/modular_doppler/wargaming/code/unit_stats/destroyer.dm index 70241476838690..5b668cab802956 100644 --- a/modular_doppler/wargaming/code/unit_stats/destroyer.dm +++ b/modular_doppler/wargaming/code/unit_stats/destroyer.dm @@ -11,7 +11,8 @@ unit_size = WARGAME_SIZE_MEDIUM /datum/wargame_unit_stats/destroyer/im_boutta_blow(obj/structure/wargame_hologram/hologram) - hologram.visible_message(span_warning("[hologram] starts to drift off course before shattering into a large debris field!")) + hologram.visible_message(span_warning("[hologram] starts to drift off course before shattering into a large debris field!"), \ + blind_message = span_warning("[hologram] starts to drift off course before shattering into a large debris field!")) return ..() /datum/wargame_unit_stats/destroyer/brawler diff --git a/modular_doppler/wargaming/code/unit_stats/missiles.dm b/modular_doppler/wargaming/code/unit_stats/missiles.dm index 19c497ab489042..ad7925bfb035d1 100644 --- a/modular_doppler/wargaming/code/unit_stats/missiles.dm +++ b/modular_doppler/wargaming/code/unit_stats/missiles.dm @@ -7,7 +7,8 @@ return "Track [pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] [rand(1,99)]" /datum/wargame_unit_stats/missile/im_boutta_blow(obj/structure/wargame_hologram/hologram) - hologram.visible_message(span_warning("[hologram] is reduced to data with a bright spark!")) + hologram.visible_message(span_warning("[hologram] is reduced to data with a bright spark!"), \ + blind_message = span_warning("[hologram] is reduced to data with a bright spark!")) return ..() /datum/wargame_unit_stats/missile/cruise diff --git a/modular_doppler/wargaming/code/unit_stats/static.dm b/modular_doppler/wargaming/code/unit_stats/static.dm index d5fe06c9b7042a..4623bcde04c5dc 100644 --- a/modular_doppler/wargaming/code/unit_stats/static.dm +++ b/modular_doppler/wargaming/code/unit_stats/static.dm @@ -16,7 +16,8 @@ return our_actions /datum/wargame_unit_stats/platform/im_boutta_blow(obj/structure/wargame_hologram/hologram) - hologram.visible_message(span_warning("[hologram] briefly becomes a new star in the night sky, before fading into a large debris field!")) + hologram.visible_message(span_warning("[hologram] briefly becomes a new star in the night sky, before fading into a large debris field!"), \ + blind_message = span_warning("[hologram] briefly becomes a new star in the night sky, before fading into a large debris field!")) return ..() /datum/wargame_unit_stats/platform/outpost diff --git a/modular_doppler/wargaming/code/unit_stats/strike.dm b/modular_doppler/wargaming/code/unit_stats/strike.dm index 3efec784bfc8f5..71367a7f637368 100644 --- a/modular_doppler/wargaming/code/unit_stats/strike.dm +++ b/modular_doppler/wargaming/code/unit_stats/strike.dm @@ -11,11 +11,12 @@ unit_size = WARGAME_SIZE_SMALL /datum/wargame_unit_stats/strike/im_boutta_blow(obj/structure/wargame_hologram/hologram) - hologram.visible_message(span_warning("[hologram] starts to careen off course before crackling and exploding!")) + hologram.visible_message(span_warning("[hologram] starts to careen off course before crackling and exploding!"), \ + blind_message = span_warning("[hologram] starts to careen off course before crackling and exploding!")) return ..() /datum/wargame_unit_stats/strike/create_unit_name() - return "[pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] Squadrom" + return "[pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] Squadron" /datum/wargame_unit_stats/strike/wing unit_class = "strike wing" diff --git a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm index ef5b9f1a34b08e..9a53c8b7cf26a5 100644 --- a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm +++ b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm @@ -41,16 +41,17 @@ /datum/wargame_weapon/proc/pick_target(mob/living/user, obj/structure/wargame_hologram/hologram) var/datum/wargaming_team/hologram_team = hologram.team_reference?.resolve() var/list/potential_targets = list() - for(var/obj/structure/wargame_hologram/other_hologram in view(attack_range, get_turf(hologram))) - if(!other_hologram.unit_stats.can_be_a_target) - continue + for(var/obj/structure/wargame_hologram/other_hologram in range(attack_range, hologram)) + if(!other_hologram.unit_stats?.can_be_a_target) + continue // Anything we target at all should have unit stats but you never know if(isnull(hologram_team)) potential_targets += other_hologram continue // If we have no team then free for all everything's a target var/datum/wargaming_team/other_hologram_team = other_hologram.team_reference?.resolve() - if(other_hologram_team == hologram_team) - continue // Cannot target friendlies otherwise - potential_targets += other_hologram + if(isnull(other_hologram_team)) + potential_targets += other_hologram + if(other_hologram_team != hologram_team) + potential_targets += other_hologram if(!length(potential_targets)) hologram.balloon_alert(user, "no targets!") return diff --git a/modular_doppler/wargaming/code/unit_weapons/beams.dm b/modular_doppler/wargaming/code/unit_weapons/beams.dm index a72ff1fa982667..48e2f50a4bb3c2 100644 --- a/modular_doppler/wargaming/code/unit_weapons/beams.dm +++ b/modular_doppler/wargaming/code/unit_weapons/beams.dm @@ -15,7 +15,8 @@ return "A high-energy beam for coring large ships with. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/anti_ship_beam/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer]'s spinal beam lens glows before piercing the sky towards [target] with a blinding flash of light!")) + firer.visible_message(span_warning("[firer]'s spinal beam lens glows before piercing the sky towards [target] with a blinding flash of light!"), \ + blind_message = span_warning("[firer]'s spinal beam lens glows before piercing the sky towards [target] with a blinding flash of light!")) /datum/wargame_weapon/anti_ship_beam/firing_voiceline() var/static/list/lines = list( @@ -41,7 +42,8 @@ return "Highly focused beams with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/pd_beam/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("Gimbaled lenses all along [firer] turn towards [target] and light up the night with a dazzling laser light show!")) + firer.visible_message(span_warning("Gimbaled lenses all along [firer] turn towards [target] and light up the night with a dazzling laser light show!"), \ + blind_message = span_warning("Gimbaled lenses all along [firer] turn towards [target] and light up the night with a dazzling laser light show!")) /datum/wargame_weapon/pd_beam/firing_voiceline() var/static/list/lines = list( diff --git a/modular_doppler/wargaming/code/unit_weapons/big_guns.dm b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm index 840e55397d16a5..ccab212850d353 100644 --- a/modular_doppler/wargaming/code/unit_weapons/big_guns.dm +++ b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm @@ -15,7 +15,8 @@ return "A large mass driver for launching hardened cores at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/mass_driver/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer]'s mass driver ripples the space around it as it charges, creating a streak across the sky as it fires at [target]!")) + firer.visible_message(span_warning("[firer]'s mass driver ripples the space around it as it charges, piercing a streak across the sky as it fires at [target]!"), \ + blind_message = span_warning("[firer]'s mass driver ripples the space around it as it charges, piercing a streak across the sky as it fires at [target]!")) /datum/wargame_weapon/mass_driver/firing_voiceline() var/static/list/lines = list( @@ -44,7 +45,8 @@ return "A large calibre 7 in. cannon for launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/large_cannon/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer]'s 7\" guns fire at [target], sending a jet of burning plasma out the back of each cannon!")) + firer.visible_message(span_warning("[firer]'s 7\" guns fire at [target], sending a jet of burning plasma out the back of each cannon!"), \ + blind_message = span_warning("[firer]'s 7\" guns fire at [target], sending a jet of burning plasma out the back of each cannon!")) /datum/wargame_weapon/large_cannon/firing_voiceline() var/static/list/lines = list( @@ -71,7 +73,8 @@ return "A medium calibre 2.5 in. cannon fore launching shells at large targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/medium_cannon/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer]'s 2.5\" guns turn to bear on [target], rattling the vessel as they unleash a volley of shells!")) + firer.visible_message(span_warning("[firer]'s 2.5\" guns turn to bear on [target], rattling the vessel as they unleash a volley of shells!"), \ + blind_message = span_warning("[firer]'s 2.5\" guns turn to bear on [target], rattling the vessel as they unleash a volley of shells!")) /datum/wargame_weapon/medium_cannon/firing_voiceline() var/static/list/lines = list( diff --git a/modular_doppler/wargaming/code/unit_weapons/bombs.dm b/modular_doppler/wargaming/code/unit_weapons/bombs.dm index c3383d94a2d391..0a14fb697f4063 100644 --- a/modular_doppler/wargaming/code/unit_weapons/bombs.dm +++ b/modular_doppler/wargaming/code/unit_weapons/bombs.dm @@ -17,7 +17,8 @@ playsound(firer, 'modular_doppler/wargaming/sound/swarm_launch.ogg', 40, TRUE) /datum/wargame_weapon/rockets/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer] lets loose a volley of rockets at [target]!")) + firer.visible_message(span_warning("[firer] lets loose a volley of rockets at [target]!"), \ + blind_message = span_warning("[firer] lets loose a volley of rockets at [target]!")) /datum/wargame_weapon/rockets/firing_voiceline() var/static/list/lines = list( @@ -45,7 +46,8 @@ playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 40, TRUE) /datum/wargame_weapon/bombs/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer] makes a run at [target], releasing a large bomb that drifts toward them!")) + firer.visible_message(span_warning("[firer] makes a run at [target], releasing a large bomb that drifts toward them!"), \ + blind_message = span_warning("[firer] makes a run at [target], releasing a large bomb that drifts toward them!")) /datum/wargame_weapon/bombs/firing_voiceline() var/static/list/lines = list( diff --git a/modular_doppler/wargaming/code/unit_weapons/missiles.dm b/modular_doppler/wargaming/code/unit_weapons/missiles.dm index 48ea82f6261f1a..0d9e2fdbabecb6 100644 --- a/modular_doppler/wargaming/code/unit_weapons/missiles.dm +++ b/modular_doppler/wargaming/code/unit_weapons/missiles.dm @@ -13,7 +13,8 @@ playsound(firer, 'modular_doppler/wargaming/sound/missile_launch.ogg', 30, TRUE) /datum/wargame_weapon/missile/cruise/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer]'s missile tubes pop open and dispense a large missile that quickly turns and burns towards [target]!")) + firer.visible_message(span_warning("[firer]'s missile tubes pop open and dispense a large missile that quickly turns and burns towards [target]!"), \ + blind_message = span_warning("[firer]'s missile tubes pop open and dispense a large missile that quickly turns and burns towards [target]!")) /datum/wargame_weapon/missile/cruise/weapon_description() return "A long range cruise missile for engaging large targets. Becomes a controllable ship if the target is beyond short range. \ @@ -40,7 +41,8 @@ Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/ramming/cruise/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer] engages its terminal stage boosters as it dives toward [target] in a spiraling loop!")) + firer.visible_message(span_warning("[firer] engages its terminal stage boosters as it dives toward [target] in a spiraling loop!"), \ + blind_message = span_warning("[firer] engages its terminal stage boosters as it dives toward [target] in a spiraling loop!")) /datum/wargame_weapon/missile/swarm weapon_name = "Swarm Missiles" @@ -63,7 +65,8 @@ Max target range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/missile/swarm/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer] ejects a bouquet of micro-missiles with a puff of gas, which break off into a swarm towards [target]!")) + firer.visible_message(span_warning("[firer] ejects a bouquet of micro-missiles with a puff of gas, which break off into a swarm towards [target]!"), \ + blind_message = span_warning("[firer] ejects a bouquet of micro-missiles with a puff of gas, which break off into a swarm towards [target]!")) /datum/wargame_weapon/missile/swarm/firing_voiceline() var/static/list/lines = list( @@ -85,7 +88,8 @@ Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/ramming/swarm/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer] swarm towards [target], burning in high-gee turns to evade defense!")) + firer.visible_message(span_warning("[firer] swarm towards [target], burning in high-gee turns to evade defense!"), \ + blind_message = span_warning("[firer] swarm towards [target], burning in high-gee turns to evade defense!")) /datum/wargame_weapon/missile/torpedo weapon_name = "Torpedo" @@ -106,7 +110,8 @@ Max target range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/missile/torpedo/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer] dispenses a large torpedo, which slowly spins up and gimbals toward [target]!")) + firer.visible_message(span_warning("[firer] dispenses a large torpedo, which slowly spins up and gimbals toward [target]!"), \ + blind_message = span_warning("[firer] dispenses a large torpedo, which slowly spins up and gimbals toward [target]!")) /datum/wargame_weapon/missile/torpedo/firing_voiceline() var/static/list/lines = list( @@ -129,4 +134,5 @@ Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/ramming/torpedo/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer] ejects its cruise stage and ignites an oversized terminal booster, speeding towards [target]!")) + firer.visible_message(span_warning("[firer] ejects its cruise stage and ignites an oversized terminal booster, speeding towards [target]!"), \ + blind_message = span_warning("[firer] ejects its cruise stage and ignites an oversized terminal booster, speeding towards [target]!")) diff --git a/modular_doppler/wargaming/code/unit_weapons/ramming.dm b/modular_doppler/wargaming/code/unit_weapons/ramming.dm index 89cbedfab744cb..1835107bf8bea8 100644 --- a/modular_doppler/wargaming/code/unit_weapons/ramming.dm +++ b/modular_doppler/wargaming/code/unit_weapons/ramming.dm @@ -15,4 +15,5 @@ return pick(lines) /datum/wargame_weapon/ramming/strike/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer] locks into a collision trajectory with [target], engines burning to full!")) + firer.visible_message(span_warning("[firer] locks into a collision trajectory with [target], engines burning to full!"), \ + blind_message = span_warning("[firer] locks into a collision trajectory with [target], engines burning to full!")) diff --git a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm index 18f795fc7bd5f9..23a4507698c9ae 100644 --- a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm +++ b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm @@ -14,7 +14,8 @@ return "Low calibre 45mm autocannons for fast moving targets. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/autocannon/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer]'s 45mm autocannon tracks toward [target], following the target for mere moments before sending a burst of shells at it!")) + firer.visible_message(span_warning("[firer]'s 45mm autocannon tracks toward [target], following the target for mere moments before sending a burst of shells at it!"), \ + blind_message = span_warning("[firer]'s 45mm autocannon tracks toward [target], following the target for mere moments before sending a burst of shells at it!")) /datum/wargame_weapon/autocannon/firing_voiceline() var/static/list/lines = list( @@ -40,7 +41,8 @@ return "Small calibre 26mm autocannons with exceptional hit chance but low damage. Max range of [attack_range] tiles. [action_point_cost] AP to fire." /datum/wargame_weapon/pdc/weapon_firing_message(obj/firer, obj/target) - firer.visible_message(span_warning("[firer]'s point defence grid quickly gimbals toward [target], filling the sky with a dense rain of tracers!")) + firer.visible_message(span_warning("[firer]'s point defence grid quickly gimbals toward [target], filling the sky with a dense rain of tracers!"), \ + blind_message = span_warning("[firer]'s point defence grid quickly gimbals toward [target], filling the sky with a dense rain of tracers!")) /datum/wargame_weapon/pdc/firing_voiceline() var/static/list/lines = list( diff --git a/modular_doppler/wargaming/icons/items.dmi b/modular_doppler/wargaming/icons/items.dmi index 486a174f99ce0a8c0d9c7aafc617ea15d937c309..5bcfe3fb11fb03c8d1037818581b27c2894fa08c 100644 GIT binary patch delta 1324 zcmV+{1=ISz3788diBL{Q4GJ0x0000DNk~Le0001B0000$2nGNE0Od4t4gdfElaVEU zf70BNnwwc%oLQENOOvvKtDg%vbO3LFSh71@=Boez1i(o|K~#90?V3GI6hRcn7h@&F zppje%7UVz+8{?s&@f1)v#UL@FSeTGVqm79ljiC?=D@99aG%>Ldg%~8F2a2cA5Cvmn zp?IOt;6)RN5gUtj-=1$X`#pO*dvkj?f4^k2J3DhbyYK(z&DTkiKp+qZN|iF+FHoq& zVhyrenT3UU-}YPQ_W=TbtoKql8jWgRQCYdkl)t*VqFtxcY38jf-}OhM^|pEG^rGL# zt5CN9_;~neIAXCq>dP4qxQ}H>W3hmD{Xs!30D2sH#kTtGx_nGyzLkgi!LI)Af17Ki zU#*?$0oOs*9~7QK5J_@!Lf1r%>7c>-HZ<&1(|_haFs}o`jMX<`_(^oM%Lfm4xIX8_ zvvDaoK4BRWsRe-eN_v&?#f)h(9<#u%{*6;(nnB2SU{SxFL&1HlvvFUW>vKQ=J3Nnq z+&Ujtos*a6V;{h}eh71R2SDTfe{SVGg&_1Qx2DP3`+3yw7EU4G$+JnE%25lDNF39y zlgY=*B;>6wB;ZGr`q+`Rf@|c@|_ndPuz{g)p-GYKSOb_~bMXKqK=4jht_c zp3j1uCt^Io77(-$v_o3xe{8+nCkdqh`QY2PuWAejBjOpsG}#H$bhuW8Mu4ZYOVW8_ zltWYAFKF~gRkb8lNmAy=vU<vnRKm#sQ}pVlEcw_f*o(22cg_>8_B>V z3ud}@#&(;mF%y=ehkbk}d)f2MwL-^B||w(JmC z-^baolZRs~=o$U(Yf}bddQHt%U0;CU{Y)m~`}?R{02os-s@&Mvq^7OB97E9DT#Bjj z0WfCIXjDEK`Ke5Wm!uYyl55TYT?`Vd#Ogu@4~g{gTs5;)XSf0f5l~)`qtN zce_+E{sTq_SZ#&Ee>cK{+ssVL)B@u1CUuC);#s<58wqFTObe!ijb1;gj1;$*ZReOLpdV^;Edxi^v`oG-?1oLfKy0;4>yLOpy25UtYO*- z7n5P~1v3G}e*Qpihmz$->YXKKmdSo-3)u~N}sNL7zkCz zxjWV(*YDV+UR#NQAqb5BZeg10+j##kC{zL&in)U)B2Xb0_M*E0000x`&2HY=g5Z;G`Y9uHELR1C-od#5N+zoDwP#FMp8c=Z*r_pFM8jVJ?BB^Pw z>CS_f<+I)UD)R1*zOuvKe^dLVw4GWBy1Oi*aC=o#HwPlBU@E}x6*T!8Y9*j&K->+8 z8?F&#y@YJuXc`9&AL9yn@pRnCjZf61!DOBZ($+fUqY~P7XSqT^0ksNq3BYG4v`3HL;(Gke5ShX2-BBMHai+^0a)e}&O#G?DTb Xyk(W5723)000000NkvXXu0mjft)Be3 From d4acf7d8598fade9174731d3afff63ca6c1f991a Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 23:15:08 -0400 Subject: [PATCH 30/36] fixes up the buyable kit --- .../holodeck_doppler_wargame_board.dmm | 54 ++++++++++++++----- .../loadout_categories/categories/toys.dm | 4 ++ modular_doppler/wargaming/code/game_kit.dm | 6 ++- modular_doppler/wargaming/code/projectors.dm | 6 +++ 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/_maps/templates/holodeck_doppler_wargame_board.dmm b/_maps/templates/holodeck_doppler_wargame_board.dmm index 75f23a69fd1229..f0e654d83f101d 100644 --- a/_maps/templates/holodeck_doppler_wargame_board.dmm +++ b/_maps/templates/holodeck_doppler_wargame_board.dmm @@ -30,7 +30,6 @@ /obj/effect/turf_decal/tile/neutral/half/contrasted{ dir = 1 }, -/obj/structure/railing, /obj/effect/turf_decal/trimline/yellow/warning, /turf/open/floor/holofloor{ base_icon_state = "smooth"; @@ -56,7 +55,11 @@ }, /obj/structure/table, /obj/item/wargame_base_station{ - pixel_y = 7 + pixel_y = 12; + pixel_x = 2 + }, +/obj/item/wargame_projector/ships/green{ + pixel_x = -8 }, /turf/open/floor/holofloor{ base_icon_state = "smooth_half"; @@ -116,15 +119,22 @@ }, /turf/open/floor/holofloor/stairs, /area/template_noop) +"p" = ( +/obj/effect/turf_decal/tile/neutral/half, +/obj/effect/turf_decal/trimline/yellow/warning{ + dir = 1 + }, +/turf/open/floor/holofloor{ + base_icon_state = "smooth_half"; + icon_state = "smooth_half"; + dir = 4 + }, +/area/template_noop) "q" = ( /obj/effect/turf_decal/tile/neutral{ dir = 4 }, /obj/structure/table, -/obj/item/wargame_projector/terrain{ - pixel_y = 6; - pixel_x = 14 - }, /obj/item/clothing/head/tajaran_hat{ pixel_y = 8; pixel_x = -4 @@ -132,6 +142,10 @@ /obj/item/clothing/head/lizard_hat/white{ pixel_x = -5 }, +/obj/item/wargame_projector/terrain{ + pixel_y = 13; + pixel_x = 14 + }, /turf/open/floor/holofloor{ base_icon_state = "smooth"; icon_state = "smooth"; @@ -219,6 +233,17 @@ dir = 4 }, /area/template_noop) +"z" = ( +/obj/effect/turf_decal/tile/neutral/half{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/yellow/warning, +/turf/open/floor/holofloor{ + base_icon_state = "smooth_half"; + icon_state = "smooth_half"; + dir = 4 + }, +/area/template_noop) "B" = ( /obj/effect/turf_decal/tile/neutral{ dir = 1 @@ -336,11 +361,15 @@ }, /obj/structure/shelf, /obj/item/wargame_projector/ships/red{ - pixel_x = 9 + pixel_x = 9; + pixel_y = 4 }, /obj/item/wargame_projector/ships{ pixel_y = 12 }, +/obj/item/wargame_projector/ships/yellow{ + pixel_y = -1 + }, /turf/open/floor/holofloor{ base_icon_state = "smooth_half"; icon_state = "smooth_half"; @@ -386,9 +415,6 @@ /area/template_noop) "Y" = ( /obj/effect/turf_decal/tile/neutral/half/contrasted, -/obj/structure/railing{ - dir = 1 - }, /obj/effect/turf_decal/trimline/yellow/warning{ dir = 1 }, @@ -437,14 +463,14 @@ g "} (4,1,1) = {" M -P +z F F F F F F -y +p R "} (5,1,1) = {" @@ -461,14 +487,14 @@ r "} (6,1,1) = {" d -P +z F F F F F F -y +p C "} (7,1,1) = {" diff --git a/modular_doppler/loadout_categories/categories/toys.dm b/modular_doppler/loadout_categories/categories/toys.dm index f5db2bca436e8b..6df9a9cca7f32b 100644 --- a/modular_doppler/loadout_categories/categories/toys.dm +++ b/modular_doppler/loadout_categories/categories/toys.dm @@ -85,6 +85,10 @@ item_path = /obj/item/instrument/harmonica restricted_roles = list(JOB_PRISONER) +/datum/loadout_item/toy/wargame_kit + name = "Portable Holographic Wargame Kit" + item_path = /obj/item/storage/briefcase/secure/wargame_kit + /// Plushie blast /datum/loadout_item/toy/plush diff --git a/modular_doppler/wargaming/code/game_kit.dm b/modular_doppler/wargaming/code/game_kit.dm index f0dd7eff24bb9c..175b3e408f9bc4 100644 --- a/modular_doppler/wargaming/code/game_kit.dm +++ b/modular_doppler/wargaming/code/game_kit.dm @@ -1,6 +1,6 @@ /obj/item/storage/briefcase/secure/wargame_kit - name = "DIY Wargaming Kit" - desc = "Contains everything an aspiring naval officer (or just huge fucking nerd) would need for a proper modern naval wargame." + name = "\improper Portable Holographic Wargaming Kit" + desc = "A bulky kit containing a base station and five holographic projectors for all of your portable wargaming needs." custom_premium_price = PAYCHECK_CREW * 2 /obj/item/storage/briefcase/secure/wargame_kit/PopulateContents() @@ -8,6 +8,8 @@ /obj/item/wargame_base_station = 1, /obj/item/wargame_projector/ships = 1, /obj/item/wargame_projector/ships/red = 1, + /obj/item/wargame_projector/ships/yellow = 1, + /obj/item/wargame_projector/ships/green = 1, /obj/item/wargame_projector/terrain = 1, ) generate_items_inside(items_inside,src) diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index 830721dc17d661..c8e5d73f988f0e 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -238,6 +238,12 @@ /obj/item/wargame_projector/ships/red holosign_color = COLOR_RED_LIGHT +/obj/item/wargame_projector/ships/yellow + holosign_color = COLOR_VIVID_YELLOW + +/obj/item/wargame_projector/ships/green + holosign_color = COLOR_VIBRANT_LIME + /obj/item/wargame_projector/terrain name = "holographic terrain projector" desc = "A handy-dandy holographic projector developed by the Port Authority Naval Command for playing wargames with, this one creates markers for space 'terrain'." From 46d426c339852581be88af02793113163a5e46e0 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sat, 23 May 2026 23:17:33 -0400 Subject: [PATCH 31/36] makes strike squadron names cooler --- modular_doppler/wargaming/code/unit_stats/strike.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modular_doppler/wargaming/code/unit_stats/strike.dm b/modular_doppler/wargaming/code/unit_stats/strike.dm index 71367a7f637368..2ae097c9964d7a 100644 --- a/modular_doppler/wargaming/code/unit_stats/strike.dm +++ b/modular_doppler/wargaming/code/unit_stats/strike.dm @@ -16,7 +16,7 @@ return ..() /datum/wargame_unit_stats/strike/create_unit_name() - return "[pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] Squadron" + return "Squadron [pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] [rand(1, 9)]" /datum/wargame_unit_stats/strike/wing unit_class = "strike wing" From 9cf2f7b20fd228fe2ec9360fe404e9a567e89241 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sun, 24 May 2026 04:36:47 -0400 Subject: [PATCH 32/36] balance like you never hoped to see before --- code/__DEFINES/~doppler_defines/wargames.dm | 2 +- .../wargaming/code/conditions/_unit_conditions.dm | 12 ++++++------ .../wargaming/code/holograms/_holograms.dm | 5 +++++ .../wargaming/code/unit_stats/_unit_stats.dm | 2 +- .../wargaming/code/unit_stats/corvette.dm | 2 ++ modular_doppler/wargaming/code/unit_stats/cruiser.dm | 2 +- .../wargaming/code/unit_stats/destroyer.dm | 3 ++- .../wargaming/code/unit_weapons/big_guns.dm | 6 +++--- .../wargaming/code/unit_weapons/missiles.dm | 9 ++++++--- 9 files changed, 27 insertions(+), 16 deletions(-) diff --git a/code/__DEFINES/~doppler_defines/wargames.dm b/code/__DEFINES/~doppler_defines/wargames.dm index ba403ebfaab4c0..08e5d8c42b0b83 100644 --- a/code/__DEFINES/~doppler_defines/wargames.dm +++ b/code/__DEFINES/~doppler_defines/wargames.dm @@ -19,7 +19,7 @@ #define WARGAME_SIZE_LARGE 3 /// The maximum amount of cover bonus a unit can have -#define WARGAME_MAX_COVER_BONUS 4 +#define WARGAME_MAX_COVER_BONUS 2 /// The maximum amount of evasion bonus a unit can have #define WARGAME_MAX_EVASION_BONUS 2 diff --git a/modular_doppler/wargaming/code/conditions/_unit_conditions.dm b/modular_doppler/wargaming/code/conditions/_unit_conditions.dm index 5df12f6a6f47bf..d3ac1528d684ae 100644 --- a/modular_doppler/wargaming/code/conditions/_unit_conditions.dm +++ b/modular_doppler/wargaming/code/conditions/_unit_conditions.dm @@ -8,7 +8,7 @@ var/condition_lifetime_left /// When the condition is applied to a unit stats, what changes -/datum/wargame_condition/proc/applied_to_unit(datum/wargame_unit_stats/stats) +/datum/wargame_condition/proc/applied_to_unit(datum/wargame_unit_stats/stats, obj/hologram) return /// When the condition is removed from a unit stats, what changes back @@ -21,15 +21,15 @@ condition_desc = "Complete failure of missile systems, safety self destruct imminent." condition_lifetime_left = 2 -/datum/wargame_condition/missile_failure/applied_to_unit(datum/wargame_unit_stats/stats) - stats.maximum_action_points = 0 // Missile is disabled +/datum/wargame_condition/missile_failure/applied_to_unit(datum/wargame_unit_stats/stats, obj/hologram) + stats.im_boutta_blow(hologram) /datum/wargame_condition/hull_damage condition_name = "Hull Damage" condition_desc = "Damage to the hull that reduces the armor class of the ship." condition_lifetime_left = 2 -/datum/wargame_condition/hull_damage/applied_to_unit(datum/wargame_unit_stats/stats) +/datum/wargame_condition/hull_damage/applied_to_unit(datum/wargame_unit_stats/stats, obj/hologram) stats.armor_class -= 1 /datum/wargame_condition/hull_damage/removed_from_unit(datum/wargame_unit_stats/stats) @@ -45,7 +45,7 @@ condition_desc = "Damage to the engines that makes movement cost an extra action point." condition_lifetime_left = 3 -/datum/wargame_condition/engine_damage/applied_to_unit(datum/wargame_unit_stats/stats) +/datum/wargame_condition/engine_damage/applied_to_unit(datum/wargame_unit_stats/stats, obj/hologram) stats.movement_cost += 1 /datum/wargame_condition/engine_damage/removed_from_unit(datum/wargame_unit_stats/stats) @@ -56,7 +56,7 @@ condition_desc = "Damage to the ship's reactor that reduces maximum action points by one." condition_lifetime_left = 4 -/datum/wargame_condition/reactor_damage/applied_to_unit(datum/wargame_unit_stats/stats) +/datum/wargame_condition/reactor_damage/applied_to_unit(datum/wargame_unit_stats/stats, obj/hologram) stats.maximum_action_points -= 1 /datum/wargame_condition/reactor_damage/removed_from_unit(datum/wargame_unit_stats/stats) diff --git a/modular_doppler/wargaming/code/holograms/_holograms.dm b/modular_doppler/wargaming/code/holograms/_holograms.dm index 11b3cb9d4ad1bc..adca6cdcf870ad 100644 --- a/modular_doppler/wargaming/code/holograms/_holograms.dm +++ b/modular_doppler/wargaming/code/holograms/_holograms.dm @@ -56,6 +56,11 @@ . += boxed_message(conditions_text) if(length(unit_stats.current_conditions) > unit_stats.conditions_limit) . += span_warning("It looks like it's nearing its limit, if these conditions don't improve by next effects phase, the ship will be lost.") + if(length(unit_stats.weaponry)) + var/weaponry_text = "" + for(var/datum/wargame_weapon/weapon as anything in unit_stats.weaponry) + weaponry_text += "[span_boldnotice("[weapon.weapon_name]")] - [weapon.weapon_description()][!isnull(weapon.maximum_ammo) ? " [weapon.maximum_ammo] shots left" : ""]
" + . += fieldset_block(span_bold("Weaponry"), weaponry_text, "boxed_message") /obj/structure/wargame_hologram/Destroy() if(projector) diff --git a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm index 4ffa3cbdcf040e..7148c212553e85 100644 --- a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -188,7 +188,7 @@ return TRUE // If our weapon is weak (PDC) then it makes our attack roll less than armor class var/datum/wargame_condition/new_condition = pick(possible_conditions) new_condition = new new_condition() - new_condition.applied_to_unit(src) + new_condition.applied_to_unit(src, hologram) current_conditions += new_condition if(attacking_hologram.unit_stats.talkative) attacking_hologram.say(good_hit_voiceline()) diff --git a/modular_doppler/wargaming/code/unit_stats/corvette.dm b/modular_doppler/wargaming/code/unit_stats/corvette.dm index 7b21c8909c3413..86a017d7f8f4c7 100644 --- a/modular_doppler/wargaming/code/unit_stats/corvette.dm +++ b/modular_doppler/wargaming/code/unit_stats/corvette.dm @@ -24,4 +24,6 @@ maximum_action_points = 3 weaponry = list( /datum/wargame_weapon/pdc, + /datum/wargame_weapon/autocannon, + /datum/wargame_weapon/missile/torpedo/small, ) diff --git a/modular_doppler/wargaming/code/unit_stats/cruiser.dm b/modular_doppler/wargaming/code/unit_stats/cruiser.dm index 44469b73fefab2..2ada996e8a33aa 100644 --- a/modular_doppler/wargaming/code/unit_stats/cruiser.dm +++ b/modular_doppler/wargaming/code/unit_stats/cruiser.dm @@ -46,7 +46,7 @@ /datum/wargame_unit_stats/cruiser/linebreaker unit_class = "battlecruiser" unit_description = "A heavily armed and armored cruiser for breaking through fortified enemy lines." - armor_class = 15 + armor_class = 14 evasion_modifier = 0 movement_cost = 2 maximum_action_points = 4 diff --git a/modular_doppler/wargaming/code/unit_stats/destroyer.dm b/modular_doppler/wargaming/code/unit_stats/destroyer.dm index 5b668cab802956..208a2bd3923494 100644 --- a/modular_doppler/wargaming/code/unit_stats/destroyer.dm +++ b/modular_doppler/wargaming/code/unit_stats/destroyer.dm @@ -18,7 +18,7 @@ /datum/wargame_unit_stats/destroyer/brawler unit_class = "destroyer" unit_description = "A heavily armored destroyer for close-in brawling engagements." - armor_class = 13 + armor_class = 12 evasion_modifier = 0 movement_cost = 1 maximum_action_points = 4 @@ -26,6 +26,7 @@ /datum/wargame_weapon/autocannon, /datum/wargame_weapon/missile/swarm, /datum/wargame_weapon/medium_cannon, + /datum/wargame_weapon/missile/torpedo/small, ) /datum/wargame_unit_stats/destroyer/beam diff --git a/modular_doppler/wargaming/code/unit_weapons/big_guns.dm b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm index ccab212850d353..f97c3c8ada978c 100644 --- a/modular_doppler/wargaming/code/unit_weapons/big_guns.dm +++ b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm @@ -1,12 +1,12 @@ /datum/wargame_weapon/mass_driver weapon_name = "Mass Driver" - attack_roll = "2d10+4" + attack_roll = "2d12+2" damage_roll_bonus = 0 attack_range = 3 evadable = TRUE small_ship_disadvantage = TRUE radial_icon_state = "weapon_guns_big" - action_point_cost = 1 + action_point_cost = 2 /datum/wargame_weapon/mass_driver/weapon_firing_sound(obj/firer) playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 40, TRUE) @@ -36,7 +36,7 @@ evadable = TRUE small_ship_disadvantage = TRUE radial_icon_state = "weapon_guns_big" - action_point_cost = 1 + action_point_cost = 2 /datum/wargame_weapon/large_cannon/weapon_firing_sound(obj/firer) playsound(firer, 'modular_doppler/wargaming/sound/cannon.ogg', 40, TRUE) diff --git a/modular_doppler/wargaming/code/unit_weapons/missiles.dm b/modular_doppler/wargaming/code/unit_weapons/missiles.dm index 0d9e2fdbabecb6..766f9d463af1ef 100644 --- a/modular_doppler/wargaming/code/unit_weapons/missiles.dm +++ b/modular_doppler/wargaming/code/unit_weapons/missiles.dm @@ -51,11 +51,11 @@ evadable = FALSE radial_icon_state = "weapon_missile_small" action_point_cost = 1 - maximum_ammo = 12 + maximum_ammo = 6 missile_type = /obj/structure/wargame_hologram/controllable/swarm_missile /datum/wargame_weapon/missile/swarm/strike - maximum_ammo = 4 + maximum_ammo = 2 /datum/wargame_weapon/missile/swarm/weapon_firing_sound(obj/firer) playsound(firer, 'modular_doppler/wargaming/sound/swarm_launch.ogg', 40, TRUE) @@ -71,7 +71,7 @@ /datum/wargame_weapon/missile/swarm/firing_voiceline() var/static/list/lines = list( "Get some swarmers on that track!", - "Try doding the third, fourth, and fifth ones this time!", + "Try dodging the third, fourth, and fifth ones this time!", "Solid track, swarmers out", ) return pick(lines) @@ -102,6 +102,9 @@ maximum_ammo = 4 missile_type = /obj/structure/wargame_hologram/controllable/torpedo +/datum/wargame_weapon/missile/torpedo/small + maximum_ammo = 2 + /datum/wargame_weapon/missile/torpedo/weapon_firing_sound(obj/firer) playsound(firer, 'modular_doppler/wargaming/sound/missile_launch.ogg', 30, TRUE) From 85523470e84f28761d75e509d59e263a5268d0cb Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Sun, 24 May 2026 14:04:10 -0400 Subject: [PATCH 33/36] fixes asteroids not exploding, sorts target list --- modular_doppler/wargaming/code/projectors.dm | 2 ++ .../code/unit_weapons/_unit_weapons.dm | 26 ++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index c8e5d73f988f0e..34a7ff7eeba742 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -178,6 +178,8 @@ reference_team = base_station.managed_teams[picked_team] reference_team.tracked_projectors += src linked_team = WEAKREF(reference_team) + else + base_station.terrain_projectors += src if(!do_after(user, 3 SECONDS, base_station)) play_menu_sound() return NONE diff --git a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm index 9a53c8b7cf26a5..60e29d1e039a95 100644 --- a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm +++ b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm @@ -41,17 +41,37 @@ /datum/wargame_weapon/proc/pick_target(mob/living/user, obj/structure/wargame_hologram/hologram) var/datum/wargaming_team/hologram_team = hologram.team_reference?.resolve() var/list/potential_targets = list() + var/list/target_ships = list() + var/list/target_missiles = list() + var/list/every_other_target = list() for(var/obj/structure/wargame_hologram/other_hologram in range(attack_range, hologram)) if(!other_hologram.unit_stats?.can_be_a_target) continue // Anything we target at all should have unit stats but you never know if(isnull(hologram_team)) - potential_targets += other_hologram + if(other_hologram.controllable && !istype(other_hologram.unit_stats, /datum/wargame_unit_stats/missile)) + target_ships += other_hologram + else if(istype(other_hologram.unit_stats, /datum/wargame_unit_stats/missile)) + target_missiles += other_hologram + else + every_other_target += other_hologram continue // If we have no team then free for all everything's a target var/datum/wargaming_team/other_hologram_team = other_hologram.team_reference?.resolve() if(isnull(other_hologram_team)) - potential_targets += other_hologram + if(other_hologram.controllable && !istype(other_hologram.unit_stats, /datum/wargame_unit_stats/missile)) + target_ships += other_hologram + else if(istype(other_hologram.unit_stats, /datum/wargame_unit_stats/missile)) + target_missiles += other_hologram + else + every_other_target += other_hologram if(other_hologram_team != hologram_team) - potential_targets += other_hologram + if(other_hologram.controllable && !istype(other_hologram.unit_stats, /datum/wargame_unit_stats/missile)) + target_ships += other_hologram + else if(istype(other_hologram.unit_stats, /datum/wargame_unit_stats/missile)) + target_missiles += other_hologram + else + every_other_target += other_hologram + // Sorts targets so ships and missiles are at the top + potential_targets = target_ships + target_missiles + every_other_target if(!length(potential_targets)) hologram.balloon_alert(user, "no targets!") return From 03c414d0baa21d6a4178b6cd38e985fca7a50729 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Tue, 26 May 2026 17:57:52 -0400 Subject: [PATCH 34/36] simultaneous turns update --- code/__DEFINES/~doppler_defines/wargames.dm | 9 ++ .../wargaming/code/base_station.dm | 108 ++++++++++++++---- .../wargaming/code/holograms/_holograms.dm | 7 +- modular_doppler/wargaming/code/projectors.dm | 1 + modular_doppler/wargaming/code/team_datums.dm | 4 + .../wargaming/code/unit_stats/_unit_stats.dm | 76 ++++++++---- .../wargaming/code/unit_stats/asteroid.dm | 2 +- .../wargaming/code/unit_stats/missiles.dm | 2 +- .../wargaming/code/unit_stats/strike.dm | 2 +- .../code/unit_weapons/_unit_weapons.dm | 6 +- .../wargaming/code/unit_weapons/beams.dm | 14 +-- .../wargaming/code/unit_weapons/big_guns.dm | 22 ++-- .../wargaming/code/unit_weapons/bombs.dm | 4 +- .../wargaming/code/unit_weapons/missiles.dm | 24 ++-- .../wargaming/code/unit_weapons/ramming.dm | 8 +- .../wargaming/code/unit_weapons/small_guns.dm | 16 +-- modular_doppler/wargaming/icons/actions.dmi | Bin 4351 -> 4785 bytes strings/~doppler/salvage_shuttle.json | 8 ++ 18 files changed, 216 insertions(+), 97 deletions(-) diff --git a/code/__DEFINES/~doppler_defines/wargames.dm b/code/__DEFINES/~doppler_defines/wargames.dm index 08e5d8c42b0b83..be834e1dc03922 100644 --- a/code/__DEFINES/~doppler_defines/wargames.dm +++ b/code/__DEFINES/~doppler_defines/wargames.dm @@ -44,6 +44,10 @@ #define BASESTATION_TEAM_NAME "Team Name" /// Change the color of the currently selected team #define BASESTATION_TEAM_COLOR "Team Color" +/// Change a team's units address them +#define BASESTATION_TEAM_COMMANDER "Team Commander Response" +/// Change a team's name prefix for automatically generated ship names +#define BASESTATION_TEAM_PREFIX "Team Ship Prefix" /// Opens a unit's movement options #define WARGAME_UNIT_MOVE "Move" @@ -51,3 +55,8 @@ #define WARGAME_UNIT_ATTACK "Attack" /// Opens a unit's special options #define WARGAME_UNIT_SPECIAL "Special" + +/// If the base station will go one team at a time until the effects phase +#define WARGAME_TURN_MODE_STANDARD "Sequential" +/// If the base station will let every team move at once before the effects phase +#define WARGAME_TURN_MODE_SIMULTANEOUS "Simultaneous" diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index ce73d03d247083..96b137274a9c72 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -21,6 +21,8 @@ var/list/teams_per_turn = list() /// What phase of the game are we currently in var/game_phase = WARGAME_PHASE_NOTHING + /// The turn mode we are using, one team at a time or all at once + var/turn_mode = WARGAME_TURN_MODE_STANDARD /// Which team's turn is it right now? var/datum/wargaming_team/team_turn /// How many turns have passed @@ -45,6 +47,8 @@ var/static/list/team_edit_radial_options = list( BASESTATION_TEAM_NAME = image(icon = WARGAME_ACTIONS_FILE, icon_state = "team_name"), BASESTATION_TEAM_COLOR = image(icon = WARGAME_ACTIONS_FILE, icon_state = "team_color"), + BASESTATION_TEAM_COMMANDER = image(icon = WARGAME_ACTIONS_FILE, icon_state = "team_commander"), + BASESTATION_TEAM_PREFIX = image(icon = WARGAME_ACTIONS_FILE, icon_state = "team_prefix"), ) /obj/item/wargame_base_station/Initialize(mapload) @@ -75,19 +79,21 @@ . = ..() . += span_notice("Interact with the menu with [EXAMINE_HINT("Alt-Click")].") // Game stats - var/game_stats_text - game_stats_text = "The game is currently in the [game_phase_2_text(game_phase)] phase.
" - if(team_turn) - game_stats_text = "It is the turn of team [team_turn.team_name].
" + var/game_stats_text = "" + game_stats_text += "The game is currently in the [game_phase_2_text(game_phase)] phase.
" + if(turn_mode == WARGAME_TURN_MODE_SIMULTANEOUS) + game_stats_text += "The game is in simultaneous turns mode, all teams may make actions.
" + else if(team_turn) + game_stats_text += "It is the turn of team [team_turn.team_name].
" game_stats_text += game_phase_2_desc(game_phase) game_stats_text += "The teams have completed [EXAMINE_HINT("[turn_counter - 1]")] total turns.
" . += fieldset_block(game_phase_2_text(game_phase, TRUE), game_stats_text, "boxed_message") // Player and team stats for(var/team as anything in managed_teams) var/datum/wargaming_team/team_datum = managed_teams[team] - var/team_display + var/team_display = "" if(length(team_datum.team_players)) - team_display = "Composed of [length(team_datum.team_players)] players:
" + team_display += "Composed of [length(team_datum.team_players)] players:
" for(var/mob/living/player_on_team as anything in team_datum.team_players) team_display += " - [player_on_team.get_visible_name()]
" else @@ -223,11 +229,17 @@ return switch(game_phase) if(WARGAME_PHASE_PLACEMENT) + switch(turn_mode) + if(WARGAME_TURN_MODE_STANDARD) + team_turn.ready_all_units() + say("Action phase, [team_turn.team_name], turn [turn_counter].") + if(WARGAME_TURN_MODE_SIMULTANEOUS) + for(var/datum/wargaming_team/starting_team as anything in just_team_datums()) + starting_team.ready_all_units() + say("Action phase, all teams, turn [turn_counter].") game_phase = WARGAME_PHASE_ACTION - team_turn.ready_all_units() - say("Action phase, [team_turn.team_name], turn [turn_counter].") if(WARGAME_PHASE_ACTION) - if(!length(teams_per_turn)) + if(!length(teams_per_turn) || turn_mode == WARGAME_TURN_MODE_SIMULTANEOUS) update_terrain_holograms() say("Effects phase, turn [turn_counter].") for(var/datum/wargaming_team/team as anything in just_team_datums()) @@ -240,14 +252,20 @@ teams_per_turn -= team_turn say("Action phase, [team_turn.team_name], turn [turn_counter].") if(WARGAME_PHASE_EFFECTS) - teams_per_turn = just_team_datums() turn_counter += 1 say("Turn [turn_counter - 1] complete, beginning turn [turn_counter].") - team_turn = teams_per_turn[1] - team_turn.ready_all_units() - teams_per_turn -= team_turn + switch(turn_mode) + if(WARGAME_TURN_MODE_STANDARD) + teams_per_turn = just_team_datums() + team_turn = teams_per_turn[1] + team_turn.ready_all_units() + teams_per_turn -= team_turn + say("Action phase, [team_turn.team_name], turn [turn_counter].") + if(WARGAME_TURN_MODE_SIMULTANEOUS) + for(var/datum/wargaming_team/starting_team as anything in just_team_datums()) + starting_team.ready_all_units() + say("Action phase, all teams, turn [turn_counter].") game_phase = WARGAME_PHASE_ACTION - say("Action phase, [team_turn.team_name], turn [turn_counter].") play_menu_sound() /// Ends the game if it is currently running @@ -265,6 +283,7 @@ /// Starts the game if it has not already been started /obj/item/wargame_base_station/proc/start_the_game(mob/living/user) + // Verify that the game can actually be started or if it has already started if(game_phase != WARGAME_PHASE_NOTHING) balloon_alert(user, "already started!") return @@ -276,21 +295,30 @@ if(number_of_valid_teams <= 1) // This counts teams that actually have players in them balloon_alert(user, "too few teams!") return + // Check if we actually want to start the game var/certainty = tgui_alert(user, "Are you certain you wish to start the game?", "Game Starter", list(MY_CHILD_WILL, MY_CHILD_WILL_NOT)) if(certainty != MY_CHILD_WILL) play_menu_sound() return + // Select what turn mode to use, sequential means one team goes at a time, simultaneous means all teams can move at once. + var/turn_mode_choice = tgui_alert(user, "Select turn mode. Sequential: One team making actions at a time, rotating. Simultaneous: All teams act at once.", "Turn Mode", list(WARGAME_TURN_MODE_STANDARD, WARGAME_TURN_MODE_SIMULTANEOUS)) + if(isnull(turn_mode_choice)) + balloon_alert(user, "no choice made!") + play_menu_sound() + return + turn_mode = turn_mode_choice + // Actually start the game now game_phase = WARGAME_PHASE_PLACEMENT for(var/team as anything in managed_teams) var/datum/wargaming_team/team_datum = managed_teams[team] if(!length(team_datum.team_players)) qdel(team_datum) managed_teams -= team // Empty teams are wiped before the game starts - shuffle(managed_teams) - teams_per_turn = just_team_datums() - team_turn = teams_per_turn[1] - team_turn.ready_all_units() - teams_per_turn -= team_turn + if(turn_mode == WARGAME_TURN_MODE_STANDARD) + shuffle(managed_teams) + teams_per_turn = just_team_datums() + team_turn = teams_per_turn[1] + teams_per_turn -= team_turn turn_counter = 1 play_menu_sound() say("Beginning game, placement phase.") @@ -330,7 +358,7 @@ if(BASESTATION_TEAM_NAME) var/new_name = tgui_input_text(user, "Give your new team a name.", "Team Name", "Unnamed Combatants") if(isnull(new_name)) - balloon_alert(user, "needs name!") + balloon_alert(user, "need name!") show_team_edit_radial(user, edited_team) play_menu_sound() return @@ -340,16 +368,38 @@ play_menu_sound() return play_menu_sound() + show_team_edit_radial(user, edited_team) edited_team.team_name = new_name if(BASESTATION_TEAM_COLOR) var/new_color = input(user, "Choose your new team color." ,"Color Selection", COLOR_PRIDE_PURPLE) as color|null if(isnull(new_color)) - balloon_alert(user, "needs color!") + balloon_alert(user, "need color!") show_team_edit_radial(user, edited_team) play_menu_sound() return play_menu_sound() + show_team_edit_radial(user, edited_team) edited_team.team_color = new_color + if(BASESTATION_TEAM_COMMANDER) + var/new_commander_title = tgui_input_text(user, "How will this team's units address the players? ALL LOWERCASE IF NOT PROPER!", "Commander Title", "fleet") + if(isnull(new_commander_title)) + balloon_alert(user, "need title!") + show_team_edit_radial(user, edited_team) + play_menu_sound() + return + play_menu_sound() + edited_team.team_commander_reference = new_commander_title + show_team_edit_radial(user, edited_team) + if(BASESTATION_TEAM_PREFIX) + var/new_prefix = tgui_input_text(user, "Choose what your ships' automatic titles will be prefixed with, such as 4CA, TTV.", "Ship Name Prefix", "4CA") + if(isnull(new_prefix)) + balloon_alert(user, "need prefix!") + show_team_edit_radial(user, edited_team) + play_menu_sound() + return + play_menu_sound() + edited_team.team_ship_prefix = new_prefix + show_team_edit_radial(user, edited_team) /// Lets players pick from a list of existing teams to delete one /obj/item/wargame_base_station/proc/delete_team(mob/living/user) @@ -388,6 +438,22 @@ return play_menu_sound() new_team.team_color = new_color + var/new_commander_title = tgui_input_text(user, "How will this team's units address the players? ALL LOWERCASE IF NOT PROPER!", "Commander Title", "fleet") + if(isnull(new_commander_title)) + balloon_alert(user, "need title!") + qdel(new_team) + play_menu_sound() + return + play_menu_sound() + new_team.team_commander_reference = new_commander_title + var/new_prefix = tgui_input_text(user, "Choose what your ships' automatic titles will be prefixed with, such as 4CA, TTV.", "Ship Name Prefix", "4CA") + if(isnull(new_prefix)) + balloon_alert(user, "need prefix!") + qdel(new_team) + play_menu_sound() + return + play_menu_sound() + new_team.team_ship_prefix = new_prefix managed_teams[new_team.team_name] += new_team /// Plays a clicking sound for menu actions diff --git a/modular_doppler/wargaming/code/holograms/_holograms.dm b/modular_doppler/wargaming/code/holograms/_holograms.dm index adca6cdcf870ad..c8900a75c300b4 100644 --- a/modular_doppler/wargaming/code/holograms/_holograms.dm +++ b/modular_doppler/wargaming/code/holograms/_holograms.dm @@ -49,11 +49,10 @@ . += "This unit has [unit_stats.action_points] / [unit_stats.maximum_action_points] action points." . += "It costs this unit [unit_stats.movement_cost] action points to move." if(length(unit_stats.current_conditions)) - . += span_notice("It currently suffers from the following conditions") var/conditions_text = "" for(var/datum/wargame_condition/condition as anything in unit_stats.current_conditions) - conditions_text += "- [condition.condition_name] - [condition.condition_desc] - [condition.condition_lifetime_left] turns until cleared.
" - . += boxed_message(conditions_text) + conditions_text += "[span_boldnotice("[condition.condition_name]")] - [condition.condition_desc] - [condition.condition_lifetime_left] turns until cleared.
" + . += fieldset_block(span_bold("Conditions"), conditions_text, "boxed_message") if(length(unit_stats.current_conditions) > unit_stats.conditions_limit) . += span_warning("It looks like it's nearing its limit, if these conditions don't improve by next effects phase, the ship will be lost.") if(length(unit_stats.weaponry)) @@ -100,6 +99,8 @@ var/datum/wargaming_team/our_team = team_reference?.resolve() if(isnull(our_team)) return FALSE + if(base_station.turn_mode == WARGAME_TURN_MODE_SIMULTANEOUS) + return TRUE if(base_station.team_turn != our_team) return FALSE return TRUE diff --git a/modular_doppler/wargaming/code/projectors.dm b/modular_doppler/wargaming/code/projectors.dm index 34a7ff7eeba742..fa6cc6496d6939 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -154,6 +154,7 @@ playsound(loc, SFX_INDUSTRIAL_SCAN, 45, TRUE) if(requires_linked_team) target_holosign.team_reference = linked_team + target_holosign.unit_stats.set_hologram_name(target_holosign) if(target_holosign.swarming) return // Ships use the swarming component and don't get specific pixel offsets var/click_x diff --git a/modular_doppler/wargaming/code/team_datums.dm b/modular_doppler/wargaming/code/team_datums.dm index fe6252882ca000..13dac1ea2c04b5 100644 --- a/modular_doppler/wargaming/code/team_datums.dm +++ b/modular_doppler/wargaming/code/team_datums.dm @@ -4,6 +4,10 @@ var/team_name /// The color of the team as shown in holograms and outfits var/team_color = "#fff" + /// How does this team's units reference their commander(s) + var/team_commander_reference = "fleet" + /// The prefix applied to all automatically generated ship names for this team + var/team_ship_prefix = "4CA" /// The players currently on this team var/list/team_players = list() /// Tracks hologram projectors associated with this team for updating unit action points with diff --git a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm index 7148c212553e85..0b836d89325563 100644 --- a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -32,6 +32,8 @@ var/unit_size = WARGAME_SIZE_SMALL /// If this unit is talkative and will speak voicelines during specific actions var/talkative = TRUE + /// How does this unit refer to the players in charge of it? Set by the team datum + var/commander = "fleet" /// Associative list of weapon to radial choice, should be a list of weapons on init var/list/weaponry = list() @@ -40,20 +42,26 @@ /// Sets the name of the attached hologram /datum/wargame_unit_stats/proc/set_hologram_name(obj/structure/wargame_hologram/hologram) + var/datum/wargaming_team/team = hologram.team_reference?.resolve() + if(!isnull(team)) + commander = team.team_commander_reference if(!isnull(unit_description)) hologram.desc = unit_description if(isnull(unit_class) && isnull(unit_name)) return if(generates_name) - unit_name = create_unit_name() + unit_name = create_unit_name(team) if(isnull(unit_name)) hologram.name = unit_class else hologram.name = "[unit_class] - ([unit_name])" /// Generates a name for the unit, overwriten by subtypes for different naming schemes -/datum/wargame_unit_stats/proc/create_unit_name() - return pick_list_replacements("~doppler/salvage_shuttle.json", "ship_name") +/datum/wargame_unit_stats/proc/create_unit_name(datum/wargaming_team/team) + if(isnull(team)) + return pick_list_replacements("~doppler/salvage_shuttle.json", "ship_name") + else + return "[team.team_ship_prefix] [pick_list_replacements("~doppler/salvage_shuttle.json", "ship_name_no_designation")]" /// Sets up weapon datums and radial options /datum/wargame_unit_stats/proc/set_up_weaponry() @@ -87,12 +95,12 @@ im_boutta_blow(hologram) return if(repaired_this_phase) - var/static/list/lines = list( - "Conditions repaired, combat functionality restored.", - "Repairs complete, let's get back in the fight!", - "Damage control reports successful repairs.", - "One less hole in the wall, let's keep at it!", - "Integrity partially restored, let's show them we're still in this!", + var/list/lines = list( + "Conditions repaired [commander], combat functionality restored.", + "Repairs complete [commander], let's get back in the fight!", + "[capitalize(commander)]? Damage control reports successful repairs.", + "One less hole in the wall [commander], let's keep at it!", + "Integrity partially restored [commander], let's show them we're still in this!", ) hologram.say(pick(lines)) playsound(hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) @@ -100,13 +108,13 @@ /// What to do when this unit explodes, good place to spawn a replacement "wreck" unit type /datum/wargame_unit_stats/proc/im_boutta_blow(obj/structure/wargame_hologram/hologram) if(talkative) - var/static/list/lines = list( + var/list/lines = list( "Reactor critical, all crew punch out!", "We're losing her!", "It's not- It's not shutting down!", "Damage critical, all crew to lifeboats.", "Red alert! Abandon ship!", - "Send for rescue! We're-", + "Send for rescue [commander]! We're-", "Punch out! Punch out! We're had!", ) hologram.say(pick(lines)) @@ -150,10 +158,10 @@ var/obj/structure/wargame_hologram/target_hologram = weapon_choice.pick_target(user, hologram) if(isnull(target_hologram)) return - if(!weapon_choice.prefire_checks(user, src, hologram)) + if(!weapon_choice.prefire_checks(user, src, hologram, target_hologram)) return - if(weapon_choice.firing_voiceline() && talkative) - hologram.say(weapon_choice.firing_voiceline()) + if(weapon_choice.firing_voiceline(src) && talkative) + hologram.say(weapon_choice.firing_voiceline(src)) playsound(hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) weapon_choice.weapon_firing_message(hologram, target_hologram) weapon_choice.weapon_firing_sound(hologram) @@ -162,6 +170,23 @@ weapon_choice.special_effects_fire(user, src, hologram, target_hologram, impact) else weapon_choice.special_effects_fire(user, src, hologram, target_hologram) + if(!isnull(weapon_choice.maximum_ammo)) + if(weapon_choice.maximum_ammo <= 0) + weaponry -= weapon_choice + report_no_ammo(hologram, weapon_choice) + qdel(weapon_choice) + +/// Speaks a voiceline about a weapon being out of ammo +/datum/wargame_unit_stats/proc/report_no_ammo(obj/structure/wargame_hologram/hologram, datum/wargame_weapon/weapon) + var/list/voicelines = list( + "[weapon], ammo depleted.", + "We're out of [weapon] [commander]!", + "[capitalize(commander)]? [weapon], reporting ammunition zero.", + "Bingo ammo on [weapon], [commander].", + "That was the last of [weapon] we had!", + ) + hologram.say(pick(voicelines)) + playsound(hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) /// Calculates getting attacked, returns TRUE if the weapon actually made impact with the enemy, even if it did nothing /datum/wargame_unit_stats/proc/get_attacked(mob/living/user, obj/structure/wargame_hologram/attacking_hologram, obj/structure/wargame_hologram/hologram, datum/wargame_weapon/weapon_used) @@ -201,27 +226,27 @@ /// Returns a voiceline for missing the target /datum/wargame_unit_stats/proc/missed_voiceline() - var/static/list/lines = list( - "Bad target track, we missed the target!", - "We have missed the target.", + var/list/lines = list( + "Bad target track, we missed the target [commander]!", + "[capitalize(commander)], we have missed the target.", "Looks like we missed!", ) return pick(lines) /// Returns a voiceline for hitting the target and doing nothing /datum/wargame_unit_stats/proc/nonpen_voiceline() - var/static/list/lines = list( + var/list/lines = list( "Hit- We didn't even scratch them!", - "No effect on the target, bad hit!", + "No effect on the target, bad hit [commander]!", "All we did was scratch the paint!", ) return pick(lines) /// Returns a voiceline for hitting the target and damaging it /datum/wargame_unit_stats/proc/good_hit_voiceline() - var/static/list/lines = list( - "Confirming good hits.", - "We hit them! Good effect on target!", + var/list/lines = list( + "Confirming good hits [commander].", + "[capitalize(commander)]! We hit them! Good effect on target!", "Yes, a hit!", ) return pick(lines) @@ -264,12 +289,15 @@ if(prob(50)) playsound(hologram, 'sound/items/radio/radio_talk.ogg', 50, TRUE) return - var/static/list/lines = list( + var/list/lines = list( "Moving to designated coordinates.", - "Moving to positions.", + "Moving to position [commander].", "Displacing.", "Getting there.", "Starting burn.", + "Move order ackowledged.", + "Course set [commander].", + "Coordinates dialed in.", ) if(talkative) hologram.say("[pick(lines)]") diff --git a/modular_doppler/wargaming/code/unit_stats/asteroid.dm b/modular_doppler/wargaming/code/unit_stats/asteroid.dm index b4696ff01626fb..4504935d7d694d 100644 --- a/modular_doppler/wargaming/code/unit_stats/asteroid.dm +++ b/modular_doppler/wargaming/code/unit_stats/asteroid.dm @@ -26,7 +26,7 @@ maximum_action_points = 0 weaponry = list() -/datum/wargame_unit_stats/terrain/asteroid/create_unit_name() +/datum/wargame_unit_stats/terrain/asteroid/create_unit_name(datum/wargaming_team/team) return "[pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] [rand(100, 999)]" /datum/wargame_unit_stats/terrain/dust_cloud diff --git a/modular_doppler/wargaming/code/unit_stats/missiles.dm b/modular_doppler/wargaming/code/unit_stats/missiles.dm index ad7925bfb035d1..6c734f3ffb2681 100644 --- a/modular_doppler/wargaming/code/unit_stats/missiles.dm +++ b/modular_doppler/wargaming/code/unit_stats/missiles.dm @@ -3,7 +3,7 @@ talkative = FALSE generates_name = TRUE -/datum/wargame_unit_stats/missile/create_unit_name() +/datum/wargame_unit_stats/missile/create_unit_name(datum/wargaming_team/team) return "Track [pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] [rand(1,99)]" /datum/wargame_unit_stats/missile/im_boutta_blow(obj/structure/wargame_hologram/hologram) diff --git a/modular_doppler/wargaming/code/unit_stats/strike.dm b/modular_doppler/wargaming/code/unit_stats/strike.dm index 2ae097c9964d7a..6844e0eaa04e96 100644 --- a/modular_doppler/wargaming/code/unit_stats/strike.dm +++ b/modular_doppler/wargaming/code/unit_stats/strike.dm @@ -15,7 +15,7 @@ blind_message = span_warning("[hologram] starts to careen off course before crackling and exploding!")) return ..() -/datum/wargame_unit_stats/strike/create_unit_name() +/datum/wargame_unit_stats/strike/create_unit_name(datum/wargaming_team/team) return "Squadron [pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] [rand(1, 9)]" /datum/wargame_unit_stats/strike/wing diff --git a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm index 60e29d1e039a95..17f35ea3c02d97 100644 --- a/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm +++ b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm @@ -26,7 +26,7 @@ return /// Returns a string for a ship to say when firing the weapon -/datum/wargame_weapon/proc/firing_voiceline() +/datum/wargame_weapon/proc/firing_voiceline(datum/wargame_unit_stats/stats) return /// Plays a firing sound for the weapon @@ -82,10 +82,12 @@ return picked_target /// If this weapon has any special pre-fire checks -/datum/wargame_weapon/proc/prefire_checks(mob/living/user, datum/wargame_unit_stats/stats, obj/structure/wargame_hologram/hologram) +/datum/wargame_weapon/proc/prefire_checks(mob/living/user, datum/wargame_unit_stats/stats, obj/structure/wargame_hologram/hologram, obj/structure/wargame_hologram/target_hologram) if(stats.action_points < action_point_cost) hologram.balloon_alert(user, "not enough AP!") return FALSE + if(get_dist(hologram, target_hologram) > attack_range) + hologram.balloon_alert(user, "out of range!") if(isnull(maximum_ammo)) return TRUE if(maximum_ammo <= 0) diff --git a/modular_doppler/wargaming/code/unit_weapons/beams.dm b/modular_doppler/wargaming/code/unit_weapons/beams.dm index 48e2f50a4bb3c2..f4f9b4c169bee1 100644 --- a/modular_doppler/wargaming/code/unit_weapons/beams.dm +++ b/modular_doppler/wargaming/code/unit_weapons/beams.dm @@ -18,10 +18,10 @@ firer.visible_message(span_warning("[firer]'s spinal beam lens glows before piercing the sky towards [target] with a blinding flash of light!"), \ blind_message = span_warning("[firer]'s spinal beam lens glows before piercing the sky towards [target] with a blinding flash of light!")) -/datum/wargame_weapon/anti_ship_beam/firing_voiceline() - var/static/list/lines = list( - "Try not to stare directly at the beam.", - "Hope you brought your sunglasses today.", +/datum/wargame_weapon/anti_ship_beam/firing_voiceline(datum/wargame_unit_stats/stats) + var/list/lines = list( + "Try not to stare directly at the beam [stats.commander].", + "Hope you brought your sunglasses today [stats.commander].", "Plasma cutters got nothing on me, watch this.", ) return pick(lines) @@ -45,10 +45,10 @@ firer.visible_message(span_warning("Gimbaled lenses all along [firer] turn towards [target] and light up the night with a dazzling laser light show!"), \ blind_message = span_warning("Gimbaled lenses all along [firer] turn towards [target] and light up the night with a dazzling laser light show!")) -/datum/wargame_weapon/pd_beam/firing_voiceline() - var/static/list/lines = list( +/datum/wargame_weapon/pd_beam/firing_voiceline(datum/wargame_unit_stats/stats) + var/list/lines = list( "Incoming track within range, blink it with the lasers.", "Get the spotlights on that track, I want it off the board!", - "Laser defence responding to incoming track.", + "Laser defence responding to incoming track [stats.commander].", ) return pick(lines) diff --git a/modular_doppler/wargaming/code/unit_weapons/big_guns.dm b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm index f97c3c8ada978c..c51004b0afc27f 100644 --- a/modular_doppler/wargaming/code/unit_weapons/big_guns.dm +++ b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm @@ -18,13 +18,13 @@ firer.visible_message(span_warning("[firer]'s mass driver ripples the space around it as it charges, piercing a streak across the sky as it fires at [target]!"), \ blind_message = span_warning("[firer]'s mass driver ripples the space around it as it charges, piercing a streak across the sky as it fires at [target]!")) -/datum/wargame_weapon/mass_driver/firing_voiceline() - var/static/list/lines = list( - "We shot this in atmosphere once, you should've seen it!", +/datum/wargame_weapon/mass_driver/firing_voiceline(datum/wargame_unit_stats/stats) + var/list/lines = list( + "We shot this in atmosphere once, you should've seen it [stats.commander]!", "One-second delivery, or your tungsten rod is free!", "Let's see how that ship looks with a brand new hole down the center of it.", - "Tungsten cube, hot and ready!", - "A few more shots like this, and we'll have to change the rails out.", + "Tungsten cube, hot and ready for them [stats.commander]!", + "[capitalize(stats.commander)], a few more shots like this, and we'll have to change the rails out.", ) return pick(lines) @@ -48,11 +48,11 @@ firer.visible_message(span_warning("[firer]'s 7\" guns fire at [target], sending a jet of burning plasma out the back of each cannon!"), \ blind_message = span_warning("[firer]'s 7\" guns fire at [target], sending a jet of burning plasma out the back of each cannon!")) -/datum/wargame_weapon/large_cannon/firing_voiceline() - var/static/list/lines = list( +/datum/wargame_weapon/large_cannon/firing_voiceline(datum/wargame_unit_stats/stats) + var/list/lines = list( "Target lead and trajectory calculations green, fire at will.", "Artillery adds dignity to what would otherwise be a vulgar brawl", - "Shell, away. Have we ceased life, saer?", + "Shell away. Have we ceased life, [stats.commander]?", ) return pick(lines) @@ -76,10 +76,10 @@ firer.visible_message(span_warning("[firer]'s 2.5\" guns turn to bear on [target], rattling the vessel as they unleash a volley of shells!"), \ blind_message = span_warning("[firer]'s 2.5\" guns turn to bear on [target], rattling the vessel as they unleash a volley of shells!")) -/datum/wargame_weapon/medium_cannon/firing_voiceline() - var/static/list/lines = list( +/datum/wargame_weapon/medium_cannon/firing_voiceline(datum/wargame_unit_stats/stats) + var/list/lines = list( "All mounts local control, watch fire for friendlies!", - "I want all guns on that track, open fire!", + "[capitalize(stats.commander)] wants all guns on that track, open fire!", "Target in range, all guns to local fire director!", ) return pick(lines) diff --git a/modular_doppler/wargaming/code/unit_weapons/bombs.dm b/modular_doppler/wargaming/code/unit_weapons/bombs.dm index 0a14fb697f4063..94fb5ef4f0f039 100644 --- a/modular_doppler/wargaming/code/unit_weapons/bombs.dm +++ b/modular_doppler/wargaming/code/unit_weapons/bombs.dm @@ -20,7 +20,7 @@ firer.visible_message(span_warning("[firer] lets loose a volley of rockets at [target]!"), \ blind_message = span_warning("[firer] lets loose a volley of rockets at [target]!")) -/datum/wargame_weapon/rockets/firing_voiceline() +/datum/wargame_weapon/rockets/firing_voiceline(datum/wargame_unit_stats/stats) var/static/list/lines = list( "Go for rocket salvo, let's see how they like some of these!", "Rockets! Rockets! You're in my party now!", @@ -49,7 +49,7 @@ firer.visible_message(span_warning("[firer] makes a run at [target], releasing a large bomb that drifts toward them!"), \ blind_message = span_warning("[firer] makes a run at [target], releasing a large bomb that drifts toward them!")) -/datum/wargame_weapon/bombs/firing_voiceline() +/datum/wargame_weapon/bombs/firing_voiceline(datum/wargame_unit_stats/stats) var/static/list/lines = list( "I got a present for ya!", "Kill him! Kill him! Point the nose at him and drop the bomb at him!", diff --git a/modular_doppler/wargaming/code/unit_weapons/missiles.dm b/modular_doppler/wargaming/code/unit_weapons/missiles.dm index 766f9d463af1ef..cdf3b071b7c0e3 100644 --- a/modular_doppler/wargaming/code/unit_weapons/missiles.dm +++ b/modular_doppler/wargaming/code/unit_weapons/missiles.dm @@ -20,11 +20,11 @@ return "A long range cruise missile for engaging large targets. Becomes a controllable ship if the target is beyond short range. \ Max target range of [attack_range] tiles. [action_point_cost] AP to fire." -/datum/wargame_weapon/missile/cruise/firing_voiceline() - var/static/list/lines = list( - "Drive signature? Ask them to hold this cruise missile for us.", - "Drive cone locked, cruise missile away.", - "Lock confirmed, launching cruise missile.", +/datum/wargame_weapon/missile/cruise/firing_voiceline(datum/wargame_unit_stats/stats) + var/list/lines = list( + "Drive signature? We'll have a cruise missile right up for them [stats.commander].", + "Drive cone locked, cruise missile away [stats.commander].", + "Lock confirmed, launching cruise missile [stats.commander].", ) return pick(lines) @@ -68,11 +68,11 @@ firer.visible_message(span_warning("[firer] ejects a bouquet of micro-missiles with a puff of gas, which break off into a swarm towards [target]!"), \ blind_message = span_warning("[firer] ejects a bouquet of micro-missiles with a puff of gas, which break off into a swarm towards [target]!")) -/datum/wargame_weapon/missile/swarm/firing_voiceline() - var/static/list/lines = list( +/datum/wargame_weapon/missile/swarm/firing_voiceline(datum/wargame_unit_stats/stats) + var/list/lines = list( "Get some swarmers on that track!", "Try dodging the third, fourth, and fifth ones this time!", - "Solid track, swarmers out", + "Solid track, swarmers out [stats.commander].", ) return pick(lines) @@ -116,11 +116,11 @@ firer.visible_message(span_warning("[firer] dispenses a large torpedo, which slowly spins up and gimbals toward [target]!"), \ blind_message = span_warning("[firer] dispenses a large torpedo, which slowly spins up and gimbals toward [target]!")) -/datum/wargame_weapon/missile/torpedo/firing_voiceline() - var/static/list/lines = list( +/datum/wargame_weapon/missile/torpedo/firing_voiceline(datum/wargame_unit_stats/stats) + var/list/lines = list( "The guns ain't enough. I want that track gone.", - "Torpedo away, stand by for fireworks.", - "Good track confirmed, torpedo dispensed.", + "Torpedo away, stand by for fireworks [stats.commander].", + "Good track confirmed, torpedo dispensed [stats.commander].", ) return pick(lines) diff --git a/modular_doppler/wargaming/code/unit_weapons/ramming.dm b/modular_doppler/wargaming/code/unit_weapons/ramming.dm index 1835107bf8bea8..c58a85d758a8ab 100644 --- a/modular_doppler/wargaming/code/unit_weapons/ramming.dm +++ b/modular_doppler/wargaming/code/unit_weapons/ramming.dm @@ -5,11 +5,11 @@ small_ship_disadvantage = TRUE action_point_cost = 2 -/datum/wargame_weapon/ramming/strike/firing_voiceline() - var/static/list/lines = list( +/datum/wargame_weapon/ramming/strike/firing_voiceline(datum/wargame_unit_stats/stats) + var/list/lines = list( "One. Last. Plan!", - "It's been an honor.", - "Initiating burn drive, godspeed.", + "It's been an honor [stats.commander].", + "Initiating burn drive, godspeed [stats.commander].", "Ah.", ) return pick(lines) diff --git a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm index 23a4507698c9ae..4657d37ece339b 100644 --- a/modular_doppler/wargaming/code/unit_weapons/small_guns.dm +++ b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm @@ -17,11 +17,11 @@ firer.visible_message(span_warning("[firer]'s 45mm autocannon tracks toward [target], following the target for mere moments before sending a burst of shells at it!"), \ blind_message = span_warning("[firer]'s 45mm autocannon tracks toward [target], following the target for mere moments before sending a burst of shells at it!")) -/datum/wargame_weapon/autocannon/firing_voiceline() - var/static/list/lines = list( +/datum/wargame_weapon/autocannon/firing_voiceline(datum/wargame_unit_stats/stats) + var/list/lines = list( "Tracking looks good, let's let 'em have some.", - "Autocannon belts spooled, ready to fire.", - "Like to see them dodge this one, shells away.", + "Autocannon belts spooled, ready to fire [stats.commander].", + "Like to see them dodge this one, shells away [stats.commander].", ) return pick(lines) @@ -44,10 +44,10 @@ firer.visible_message(span_warning("[firer]'s point defence grid quickly gimbals toward [target], filling the sky with a dense rain of tracers!"), \ blind_message = span_warning("[firer]'s point defence grid quickly gimbals toward [target], filling the sky with a dense rain of tracers!")) -/datum/wargame_weapon/pdc/firing_voiceline() - var/static/list/lines = list( +/datum/wargame_weapon/pdc/firing_voiceline(datum/wargame_unit_stats/stats) + var/list/lines = list( "We need PDC on that target, now!", - "One wall of lead, coming right up.", - "Tracking multiple targets, open fire.", + "One wall of lead, coming right up [stats.commander].", + "Tracking multiple targets [stats.commander], open fire.", ) return pick(lines) diff --git a/modular_doppler/wargaming/icons/actions.dmi b/modular_doppler/wargaming/icons/actions.dmi index 89a23e4cb9ffe4f608380923335ee8bd81b6a804..f2c3bfdc391750d2a4aa2c172ae220f68167287d 100644 GIT binary patch literal 4785 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 literal 4351 zcmY+Ic`y_X`@o51t;K%jisZgg!1)aU3feZkc{b%U8QwRgi`m8wsU7RCz=U^enhA zsZd&YzYCW6q#?5=nm4?hg>345@>Eb0q~9^s4a&Rv9MV2|SKA;5PPUhFu$(d5EYiBB zdhWP&MxuehQyGNnm4}7L&J`XRYYsX`nn5d!Do#J)nD5uK=HSoapN;lEz?u!35$}9n zuaLxy{rFKkk({>f18axhI6^#LC!S=#6cmdqm38Ne=GuOhV`5Cb>}q(2Cf!keVsdDd7Jqoxbs9_qPn;Mw>Vgp84S~u1WKkMvA|S& zh!l%3Sn43fbLyZBmoz9zTS<8;{|J=zhtUnJDXSvz9;pNAO;r|1&5P!UK7U5H=Zr|l ztnW4^l+=YM?YEJMU0mSnlNuTtUv~3p6l>c~K^CI_`TxoTmaaGJT%#XHB6rd?Iybi? z=@lEaR{xz}@qrP)`pNyg=tR5K=NJLY3e-g1~mG|LI)* z*i~tQW!@QFKHXcXS~bi~#7$#@DHh*8^3zTQJAB9p($7hCUlF#QS_Vth(5tZ}2W!zT zG6@~{ooR2v4Jp?crC6+keZRK3!qs@`iYjmETk$MEr@o5AqtmAi#}xVP=SW@Sx6nIW zozCgJqJ5!@iM9h|`HtU5RVR=|!*B|B5wl`sgLcNEJrnb+vT{@MHX>aH~(?&ZD<~>;{Jfi)qi#qOH zB)SSc_8-L%+^rV6sP@z!+RvKBv4zOON-l#EN(}*S2Q-mdGrq+c#dJ>8);`tgBglu9r~Cl4tS03L7@NF{1L% z_uk!}HqB;U8F3)>sD+d_!=1vS;wBglZ>g!`= z(@4(z6)p>`N{B9h6!SJ9n1M0dZWuC7u?$OHSB?wAhXu%Z~c1fEY}decgG znAbGa=%0PN4hTfV5jLM#B&l5tkSKz*R#?S_xSWi0TwdqXfvvFal;LxnA&&t!-)~jLyx)^f6h+MzeqAX!x=H^Z=so z;m0o#BCoP{G;VoK4DC~H-!t#egG6PU8n@S@RI={#INiS$eZA*9(j{tYT~s{S*OsI% zmHD}@oM|bY7OGdLj6I(Laa;UT$A?^I9OEN2M;&2&sbTM|g;O3XN-6MSYggcmn&NZ+ zTK@l1;6H(CO!BB-?pyTX>h03ht;kbUQ$aOL*%a7r_=$|*Al+kQtb4o zNnEyoKQH&<(A{;Lz`OZw?gg6QMAqUP5pADVR~`6rx$jU^8vap1up3d?)sZ zp861QV)lo-zj&lwt_Vrfn9fE7beTrMBBO%*0SED zn(gS#7&sUj^s4|a-v5BHk46r(h^6^H);Qk7^BeGoQhR+_I=wktq@5HV)S}o?jPs8o zqQ;L#Fndr(yxzNHBG*i|CY^XZeJGR!l9Pb};BG zxB0Br?|r3-dfy0X^9H6RQXepc@S8KCxJpWomH}#SS_-v6Z5unRIm&w}!97vJ9pjS& zUwynHJ^wg*RmnR2mdni4Yn!>{1OjJm%>~+;1Fodf2QCgq!mHcM%a|P}%KJfbO3D}P z+#%xa4v4^CnQQAfMMH?y1P4=1EWI6luKIju%O>B#VHJ(wMV28Dil6)fdmhiy z3*Kih`4XNsrT}rdEED>C>iX3@H&M~nAJ)|;tcnjqU%v#}yqlyLvcp@qa=+Qvug?e6 zd{p|a7d;cEOd1Xjo7D>3i;mJTEs?tt zaHGs|Ld~Y|*Rd*MgmhhIFo*N_;Dvktpp9fOb$4jcrL}SYc!94z=)^{{mfi3s$VUWB15v#*mp?S zT#$y}yK-L#n7(eNc&N6<4i86c0=pxjhax%Ry z4r&{NpX53|U8aKd<5WjIR=#{*rA)nR6z-rMdd-^9lFx#)_d4$2u&U7XQrZ4#v=cg5BPYI@L7(#r~f~|&%W_C1| zob4@NQ{+CpD-A6gwis`_6D8TIt!Giv!oV-YZwou}-)Rzs&LauB_(EUnVV9NYdz{E~ zjv02!)K8nOwcy*bXP=1Zx;R2rHX0Ay2vUaQT2d6m&(@LY_jzQCwvst!)#sSi5UaCT zv(^uvzZ$3A02+Up468G1587>+4Kh|e7W7fC3v?aM63QPTkDdpb*mBJ%g;a! z?>?|_Pc7^#VV9a5Y4JLVa&<~Cb+|2|-3sPG4JuFTR=v$K#>#aCq{*pFjXcPS?*yO@ z-$Fx(eQB0=9CjY{{Alyr$-KS5;wZHPFw`Fr)2$C~E8gm@sW6Gcal3q0lPH$#fAy~a zzMf6gZCOTZJyZH;)}lI2W5y!|^n-^HR%vf;+06AGcDVize`R{lF~;PUwV^QlmNpMr#xgLO>n z&G;ROmn|=y2s{k6YA))hL2lPuM;!#W<0t$C;a zPA&jSlFwzAMZ7716*2u2VKKF{AZ9XD`BwQ1K*PJIb)>K<>Uf%LVv0OFOZH$INa;-v z9@hWWPpB9)j=(WIR`A0$t+_-_P-r8UD@@{c#$h! zNx(~jQWJ7o#^678m=Zh-xJa{z59*)O9Y~Y5KZBG0;}Hnr)&@6jmQ!ocr(QgH0UBOR zi1xcu$|XaS(Jz($L09StlT@vziDv#4`t1fz+4CMfHjBshjKe1utO!@N!G8b`6TvD6 z!~}q7v0Ac_pzpMB^GgFoQ{_vhY1J((Vt?yP1!NfMD4n^_^OZdtoicB=1+y{#o9&d% zO_u;PLvP>TQ=%P~o&oPaL!~b2?jjvp^Y6XD)yD7~omGQLK)<&oAH@{|2Gn8%0y@ow z9$2}`$QtbDO={KsFIfM@**x!vnUvd~c!rR7pm(0GnjF@v99H{Jb!!2koZs|P6IKC4 zrE%MA;oYdGCiBnY-LOx1d4$RKI($W7mDUscg8qC@Ih%*XC%|5=-d^h3`}_ z^rp||xW^9L*_d}0Zr|MrKX9_wW%j^~o={GvJa7)}-TZM}OVNM22Gmca=x!cQ4%J2 zA-eSU{M~B%oSdr}t+#tOspBlnJadVOPd)LkTfUKSe2}?d9>;3TEzVN-%A*W}50i3X zl#t|MOa37^f6exn3?X?eUtUNnx*MQW@;TmQo7|w$1C%}Yl#@j)sKF{il9-(m)T7i_ zF*Lc#v%pY{Rk=Rsj~*r2vj_`zcGD2w?d+~^Oxag7rSFeY(e2o>FxOdu!$#-82sTT` zk2taJ4!Oe@^eu+k0}(F7 zaIsegXt1aZCx$D_4Zh1*Tp197dw@AsWv~8wK*fTDbEa4h<@YLJ&>*vG8%BYEKH$##M z3M^a>(hH#G$jDSq1k diff --git a/strings/~doppler/salvage_shuttle.json b/strings/~doppler/salvage_shuttle.json index 8a723c8b558116..3264ab3f83182f 100644 --- a/strings/~doppler/salvage_shuttle.json +++ b/strings/~doppler/salvage_shuttle.json @@ -7,6 +7,14 @@ "@pick(designation) @pick(name_word)-@pick(name_word) @pick(digit)" ], + "ship_name_no_designation": [ + "@pick(name_word) @pick(digit)", + "@pick(name_word) @pick(name_word)", + "@pick(name_word) @pick(name_word) @pick(digit)", + "@pick(name_word)-@pick(name_word)", + "@pick(name_word)-@pick(name_word) @pick(digit)" + ], + "designation": [ "4CA", "4CAV", From 720080d9fc2f7dea3e7718925a6fcbae3150023e Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Tue, 26 May 2026 21:02:00 -0400 Subject: [PATCH 35/36] fixes lots of the new voiceline stuff --- code/__DEFINES/~doppler_defines/wargames.dm | 2 +- .../wargaming/code/unit_stats/_unit_stats.dm | 34 ++++++++++--------- .../wargaming/code/unit_weapons/ramming.dm | 7 +++- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/code/__DEFINES/~doppler_defines/wargames.dm b/code/__DEFINES/~doppler_defines/wargames.dm index be834e1dc03922..e81f0f6ac103e6 100644 --- a/code/__DEFINES/~doppler_defines/wargames.dm +++ b/code/__DEFINES/~doppler_defines/wargames.dm @@ -21,7 +21,7 @@ /// The maximum amount of cover bonus a unit can have #define WARGAME_MAX_COVER_BONUS 2 /// The maximum amount of evasion bonus a unit can have -#define WARGAME_MAX_EVASION_BONUS 2 +#define WARGAME_MAX_EVASION_BONUS 1 /// Add a team to the base station #define BASESTATION_ADD_TEAM "Add Team" diff --git a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm index 0b836d89325563..b31b4d684f01f7 100644 --- a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -179,11 +179,11 @@ /// Speaks a voiceline about a weapon being out of ammo /datum/wargame_unit_stats/proc/report_no_ammo(obj/structure/wargame_hologram/hologram, datum/wargame_weapon/weapon) var/list/voicelines = list( - "[weapon], ammo depleted.", - "We're out of [weapon] [commander]!", - "[capitalize(commander)]? [weapon], reporting ammunition zero.", - "Bingo ammo on [weapon], [commander].", - "That was the last of [weapon] we had!", + "[weapon.weapon_name], ammo depleted.", + "We're out of [weapon.weapon_name] [commander]!", + "[capitalize(commander)]? [weapon.weapon_name], reporting ammunition zero.", + "Bingo ammo on [weapon.weapon_name], [commander].", + "That was the last of [weapon.weapon_name] we had!", ) hologram.say(pick(voicelines)) playsound(hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) @@ -198,14 +198,14 @@ var/total_armor_class = weapon_used.evadable ? (calculate_armor_class(hologram) + calculate_evasion_mod(hologram)) : calculate_armor_class(hologram) if(incoming_attack_roll <= total_armor_class) if(attacking_hologram.unit_stats.talkative) - attacking_hologram.say(missed_voiceline()) + attacking_hologram.say(missed_voiceline(attacking_hologram.unit_stats)) playsound(attacking_hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) hologram.visible_message(span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was less than or equal to the target's effective armor class, [total_armor_class]."), \ blind_message = span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was less than or equal to the target's effective armor class, [total_armor_class].")) return FALSE if((incoming_attack_roll + weapon_used.damage_roll_bonus) <= armor_class) if(attacking_hologram.unit_stats.talkative) - attacking_hologram.say(nonpen_voiceline()) + attacking_hologram.say(nonpen_voiceline(attacking_hologram.unit_stats)) playsound(attacking_hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) hologram.visible_message(span_warning("Attacker's damage roll, [incoming_attack_roll + weapon_used.damage_roll_bonus], was less than or equal to the target's armor class, [armor_class]."), \ blind_message = span_warning("Attacker's damage roll, [incoming_attack_roll + weapon_used.damage_roll_bonus], was less than or equal to the target's armor class, [armor_class].")) @@ -216,7 +216,7 @@ new_condition.applied_to_unit(src, hologram) current_conditions += new_condition if(attacking_hologram.unit_stats.talkative) - attacking_hologram.say(good_hit_voiceline()) + attacking_hologram.say(good_hit_voiceline(attacking_hologram.unit_stats)) playsound(attacking_hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) hologram.visible_message(span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was higher than the target's effective armor class, [total_armor_class]."), \ blind_message = span_warning("Attacker's roll, [weapon_used.attack_roll], resulted in [incoming_attack_roll], which was higher than the target's effective armor class, [total_armor_class].")) @@ -225,28 +225,28 @@ return TRUE /// Returns a voiceline for missing the target -/datum/wargame_unit_stats/proc/missed_voiceline() +/datum/wargame_unit_stats/proc/missed_voiceline(datum/wargame_unit_stats/stats) var/list/lines = list( - "Bad target track, we missed the target [commander]!", - "[capitalize(commander)], we have missed the target.", + "Bad target track, we missed the target [stats.commander]!", + "[capitalize(stats.commander)], we have missed the target.", "Looks like we missed!", ) return pick(lines) /// Returns a voiceline for hitting the target and doing nothing -/datum/wargame_unit_stats/proc/nonpen_voiceline() +/datum/wargame_unit_stats/proc/nonpen_voiceline(datum/wargame_unit_stats/stats) var/list/lines = list( "Hit- We didn't even scratch them!", - "No effect on the target, bad hit [commander]!", + "No effect on the target, bad hit [stats.commander]!", "All we did was scratch the paint!", ) return pick(lines) /// Returns a voiceline for hitting the target and damaging it -/datum/wargame_unit_stats/proc/good_hit_voiceline() +/datum/wargame_unit_stats/proc/good_hit_voiceline(datum/wargame_unit_stats/stats) var/list/lines = list( - "Confirming good hits [commander].", - "[capitalize(commander)]! We hit them! Good effect on target!", + "Confirming good hits [stats.commander].", + "[capitalize(stats.commander)]! We hit them! Good effect on target!", "Yes, a hit!", ) return pick(lines) @@ -256,6 +256,8 @@ var/cover_modifier = 0 var/turf/cover_turf = get_turf(hologram) for(var/obj/structure/wargame_hologram/cover_hologram in cover_turf.contents) + if(cover_hologram == hologram) + continue if(cover_hologram.unit_stats.unit_size > unit_size) cover_modifier++ return armor_class + min(cover_modifier, WARGAME_MAX_COVER_BONUS) diff --git a/modular_doppler/wargaming/code/unit_weapons/ramming.dm b/modular_doppler/wargaming/code/unit_weapons/ramming.dm index c58a85d758a8ab..6d198f097e85b5 100644 --- a/modular_doppler/wargaming/code/unit_weapons/ramming.dm +++ b/modular_doppler/wargaming/code/unit_weapons/ramming.dm @@ -3,7 +3,12 @@ damage_roll_bonus = 0 evadable = TRUE small_ship_disadvantage = TRUE - action_point_cost = 2 + action_point_cost = 1 + +/datum/wargame_weapon/ramming/strike/weapon_description() + return "Order the wing to fly into the target in a suicide attack. This is a one-way trip if successful. \ + Max range of [attack_range] tiles. [action_point_cost] AP to fire." + /datum/wargame_weapon/ramming/strike/firing_voiceline(datum/wargame_unit_stats/stats) var/list/lines = list( From d0e8b2e11765776e3e0d405c1e088e36809869c3 Mon Sep 17 00:00:00 2001 From: Paxilmaniac Date: Thu, 28 May 2026 16:50:25 -0400 Subject: [PATCH 36/36] allows joining/leaving teams mid-match --- modular_doppler/wargaming/code/base_station.dm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modular_doppler/wargaming/code/base_station.dm b/modular_doppler/wargaming/code/base_station.dm index 96b137274a9c72..ff62da626101b6 100644 --- a/modular_doppler/wargaming/code/base_station.dm +++ b/modular_doppler/wargaming/code/base_station.dm @@ -41,6 +41,7 @@ /// Radial options for after the game has already been started var/static/list/mid_game_radial_options = list( BASESTATION_END = image(icon = WARGAME_ACTIONS_FILE, icon_state = "end_game"), + BASESTATION_JOIN_LEAVE = image(icon = WARGAME_ACTIONS_FILE, icon_state = "join_team"), BASESTATION_NEXT = image(icon = WARGAME_ACTIONS_FILE, icon_state = "next_phase"), ) /// Radial options for editing a team @@ -179,9 +180,6 @@ /// Allows the user to join or leave a team of their choosing /obj/item/wargame_base_station/proc/try_join_leave(mob/living/user) - if(game_phase != WARGAME_PHASE_NOTHING) - balloon_alert(user, "already in progress!") - return var/option = tgui_alert(user, "Join or leave a team?", "Team Manager", list(JOIN_A_TEAM, LEAVE_A_TEAM)) if(isnull(option)) balloon_alert(user, "no choice made!")