diff --git a/_maps/templates/holodeck_doppler_wargame_board.dmm b/_maps/templates/holodeck_doppler_wargame_board.dmm new file mode 100644 index 00000000000000..f0e654d83f101d --- /dev/null +++ b/_maps/templates/holodeck_doppler_wargame_board.dmm @@ -0,0 +1,535 @@ +//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/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 = 12; + pixel_x = 2 + }, +/obj/item/wargame_projector/ships/green{ + pixel_x = -8 + }, +/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) +"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/clothing/head/tajaran_hat{ + pixel_y = 8; + pixel_x = -4 + }, +/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"; + 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) +"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 + }, +/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/indestructible/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/ships/red{ + 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"; + 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/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 +z +F +F +F +F +F +F +p +R +"} +(5,1,1) = {" +k +e +F +F +F +F +F +F +Y +r +"} +(6,1,1) = {" +d +z +F +F +F +F +F +F +p +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/code/__DEFINES/~doppler_defines/wargames.dm b/code/__DEFINES/~doppler_defines/wargames.dm new file mode 100644 index 00000000000000..e81f0f6ac103e6 --- /dev/null +++ b/code/__DEFINES/~doppler_defines/wargames.dm @@ -0,0 +1,62 @@ +#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 +#define WARGAME_PHASE_ACTION 1 +/// Current game phase is effects +#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 + +/// 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 2 +/// The maximum amount of evasion bonus a unit can have +#define WARGAME_MAX_EVASION_BONUS 1 + +/// 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" +/// 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" +/// 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" +/// Opens a unit's attack options +#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/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/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" 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/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 new file mode 100644 index 00000000000000..ff62da626101b6 --- /dev/null +++ b/modular_doppler/wargaming/code/base_station.dm @@ -0,0 +1,462 @@ +/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" + 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 + 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() + /// 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 + /// 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 + 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 = 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 = 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 + 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) + . = ..() + 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) + 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" + 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. \ + 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) + +/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.
" + 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 = "" + 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)) + 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) + 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.
" + +/// 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" + +#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) + 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 + 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) + 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 + +/// 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) + 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 + if(WARGAME_PHASE_ACTION) + 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()) + team.update_all_units() + game_phase = WARGAME_PHASE_EFFECTS + else + game_phase = WARGAME_PHASE_ACTION + 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) + turn_counter += 1 + say("Turn [turn_counter - 1] complete, beginning turn [turn_counter].") + 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 + play_menu_sound() + +/// 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) + 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 +/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 + 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 + // 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 + 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.") + +/// 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) + 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 +/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, "need 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() + 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, "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) + 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) + 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!") + 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 + 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 +/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/conditions/_unit_conditions.dm b/modular_doppler/wargaming/code/conditions/_unit_conditions.dm new file mode 100644 index 00000000000000..d3ac1528d684ae --- /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, obj/hologram) + 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, 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, obj/hologram) + 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 = 3 + +/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) + 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 = 4 + +/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) + 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..175b3e408f9bc4 100644 --- a/modular_doppler/wargaming/code/game_kit.dm +++ b/modular_doppler/wargaming/code/game_kit.dm @@ -1,29 +1,15 @@ /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() 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/ships/yellow = 1, + /obj/item/wargame_projector/ships/green = 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/holodeck.dm b/modular_doppler/wargaming/code/holodeck.dm new file mode 100644 index 00000000000000..25b92cce7338ea --- /dev/null +++ b/modular_doppler/wargaming/code/holodeck.dm @@ -0,0 +1,9 @@ +/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" + template_id = "holodeck_doppler_wargame_board" + mappath = "_maps/templates/holodeck_doppler_wargame_board.dmm" diff --git a/modular_doppler/wargaming/code/holograms.dm b/modular_doppler/wargaming/code/holograms.dm deleted file mode 100644 index 1d85021ef680c9..00000000000000 --- a/modular_doppler/wargaming/code/holograms.dm +++ /dev/null @@ -1,122 +0,0 @@ -/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." - icon = 'modular_doppler/wargaming/icons/projectors_and_holograms.dmi' - icon_state = null - anchored = TRUE - density = FALSE - max_integrity = 1 - 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 - -/obj/structure/wargame_hologram/Initialize(mapload, source_projector) - . = ..() - if(source_projector) - projector = source_projector - LAZYADD(projector.projections, src) - -/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) - -/// Projections for 'moving vessels' in order from smallest to largest representation - -/obj/structure/wargame_hologram/strike_craft - name = "strike craft marker" - desc = "A hologram of a single strike craft." - icon_state = "strikesingle" - -/obj/structure/wargame_hologram/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 - name = "strike craft wing marker" - desc = "A hologram of a wing of strike craft." - icon_state = "strikewing" - -/obj/structure/wargame_hologram/ship_marker - name = "small vessel marker" - desc = "A hologram of a small frigate." - icon_state = "smallship" - -/obj/structure/wargame_hologram/ship_marker/medium - name = "medium vessel marker" - desc = "A hologram of a destroyer." - icon_state = "mediumship" - -/obj/structure/wargame_hologram/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 - name = "alternate large vessel marker" - desc = "A hologram of a massive ship." - icon_state = "bigship_alternate" - -/obj/structure/wargame_hologram/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..c8900a75c300b4 --- /dev/null +++ b/modular_doppler/wargaming/code/holograms/_holograms.dm @@ -0,0 +1,120 @@ +/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 + 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 + 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 + register_context() + +/obj/structure/wargame_hologram/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + 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 + +/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)) + var/conditions_text = "" + for(var/datum/wargame_condition/condition as anything in unit_stats.current_conditions) + 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)) + 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) + 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!") + playsound(src, 'sound/effects/seedling_chargeup.ogg', 50) + 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.turn_mode == WARGAME_TURN_MODE_SIMULTANEOUS) + return TRUE + 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 18729d3d1913a7..fa6cc6496d6939 100644 --- a/modular_doppler/wargaming/code/projectors.dm +++ b/modular_doppler/wargaming/code/projectors.dm @@ -1,23 +1,21 @@ /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." - icon = 'modular_doppler/wargaming/icons/projectors_and_holograms.dmi' + name = "holographic wargame projector" + desc = "A holographic projectors for creating holograms that work in the wargaming system." + icon = 'modular_doppler/wargaming/icons/items.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" + slot_flags = ITEM_SLOT_BELT w_class = WEIGHT_CLASS_SMALL - throwforce = 0 - throw_speed = 3 - throw_range = 7 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 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 @@ -44,30 +42,46 @@ /// 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) . = ..() - AddElement(/datum/element/openspace_item_click_handler) update_appearance() populate_radial_choice_lists() + register_context() -/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/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + 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() +/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) . = ..() - 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.") - . += span_warning("Control clicking the projector will allow you to clear all active holograms.") + 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." + 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.") + +/// 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)) @@ -77,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, @@ -84,23 +99,26 @@ require_near = TRUE, tooltips = TRUE, ) - 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) /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") + 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 @@ -108,92 +126,136 @@ 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? -/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 + 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 + 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 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) + 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!") + play_menu_sound() + return NONE + 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 + 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, 1)) + if(!isturf(interacting_with)) + return ..() + if(!check_can_place_hologram(interacting_with, user)) + play_menu_sound() return NONE create_hologram(interacting_with, user, modifiers) return ITEM_INTERACT_SUCCESS /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 /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 + 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_marker + holosign_type = /obj/structure/wargame_hologram/controllable/strike_wing holosign_options = list( - /obj/structure/wargame_hologram/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/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 /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'." - 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 holosign_options = list( - /obj/structure/wargame_hologram/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/team_datums.dm b/modular_doppler/wargaming/code/team_datums.dm new file mode 100644 index 00000000000000..13dac1ea2c04b5 --- /dev/null +++ b/modular_doppler/wargaming/code/team_datums.dm @@ -0,0 +1,28 @@ +/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" + /// 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 + 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 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) + if(hologram.controllable) + hologram.unit_stats.effects_phase_process(hologram) diff --git a/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm new file mode 100644 index 00000000000000..b31b4d684f01f7 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/_unit_stats.dm @@ -0,0 +1,324 @@ +/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 + /// 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 + 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 + /// 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 + /// 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() + +/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) + 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(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(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() + 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() + 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(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/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) + +/// 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/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 [commander]! We're-", + "Punch out! Punch out! We're had!", + ) + hologram.say(pick(lines)) + 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) + +/// 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"), // To come later + ) + 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, hologram, target_hologram)) + return + 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) + 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) + 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.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) + +/// 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 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 ? (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.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.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].")) + 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_condition.applied_to_unit(src, hologram) + current_conditions += new_condition + if(attacking_hologram.unit_stats.talkative) + 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].")) + 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(datum/wargame_unit_stats/stats) + var/list/lines = list( + "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/stats) + var/list/lines = list( + "Hit- We didn't even scratch them!", + "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/stats) + var/list/lines = list( + "Confirming good hits [stats.commander].", + "[capitalize(stats.commander)]! 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 == hologram) + continue + 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) + 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) + if(prob(50)) + playsound(hologram, 'sound/items/radio/radio_talk.ogg', 50, TRUE) + return + var/list/lines = list( + "Moving to designated coordinates.", + "Moving to position [commander].", + "Displacing.", + "Getting there.", + "Starting burn.", + "Move order ackowledged.", + "Course set [commander].", + "Coordinates dialed in.", + ) + if(talkative) + hologram.say("[pick(lines)]") + playsound(hologram, 'sound/items/radio/radio_receive.ogg', 50, TRUE) + +/datum/wargame_unit_stats/generic + unit_class = "Generic Thing" + generates_name = TRUE + 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 + unit_size = WARGAME_SIZE_MEDIUM + weaponry = list( + /datum/wargame_weapon/medium_cannon, + /datum/wargame_weapon/pdc, + ) 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..4504935d7d694d --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/asteroid.dm @@ -0,0 +1,41 @@ +/datum/wargame_unit_stats/terrain + abstract_type = /datum/wargame_unit_stats/terrain + conditions_limit = 4 + possible_conditions = list( + /datum/wargame_condition/hull_damage/asteroid, + ) + 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!"), \ + blind_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." + 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(datum/wargaming_team/team) + 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." + 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..86a017d7f8f4c7 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/corvette.dm @@ -0,0 +1,29 @@ +/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!"), \ + blind_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, + /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 new file mode 100644 index 00000000000000..2ada996e8a33aa --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/cruiser.dm @@ -0,0 +1,60 @@ +/datum/wargame_unit_stats/cruiser + abstract_type = /datum/wargame_unit_stats/cruiser + 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_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!"), \ + 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 + 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 = 14 + 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..208a2bd3923494 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/destroyer.dm @@ -0,0 +1,56 @@ +/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!"), \ + blind_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 = 12 + 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_weapon/missile/torpedo/small, + ) + +/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..6c734f3ffb2681 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/missiles.dm @@ -0,0 +1,63 @@ +/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(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) + 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 + 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 = 3 + 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..4623bcde04c5dc --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/static.dm @@ -0,0 +1,61 @@ +/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!"), \ + 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 + 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..6844e0eaa04e96 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_stats/strike.dm @@ -0,0 +1,47 @@ +/datum/wargame_unit_stats/strike + abstract_type = /datum/wargame_unit_stats/strike + 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/strike/im_boutta_blow(obj/structure/wargame_hologram/hologram) + 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(datum/wargaming_team/team) + return "Squadron [pick_list_replacements("~doppler/wargame_identifiers.json", "name_word")] [rand(1, 9)]" + +/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 = 3 + 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 = 3 + weaponry = list( + /datum/wargame_weapon/pdc, + /datum/wargame_weapon/autocannon, + /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 new file mode 100644 index 00000000000000..17f35ea3c02d97 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/_unit_weapons.dm @@ -0,0 +1,135 @@ +/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(datum/wargame_unit_stats/stats) + return + +/// Plays a firing sound for the weapon +/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() + 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)) + 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)) + 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) + 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 + 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, 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) + 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) + 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, 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) + 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..f4f9b4c169bee1 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/beams.dm @@ -0,0 +1,54 @@ +/datum/wargame_weapon/anti_ship_beam + weapon_name = "Anti-Ship Beam" + attack_roll = "2d12+7" + damage_roll_bonus = 0 + attack_range = 1 + evadable = FALSE + small_ship_disadvantage = TRUE + radial_icon_state = "weapon_beam" + 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) + +/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!"), \ + 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(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) + +/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', 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." + +/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!"), \ + 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(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 [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 new file mode 100644 index 00000000000000..c51004b0afc27f --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/big_guns.dm @@ -0,0 +1,85 @@ +/datum/wargame_weapon/mass_driver + weapon_name = "Mass Driver" + 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 = 2 + +/datum/wargame_weapon/mass_driver/weapon_firing_sound(obj/firer) + 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." + +/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, 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(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 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) + +/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 = 2 + +/datum/wargame_weapon/large_cannon/weapon_firing_sound(obj/firer) + 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." + +/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!"), \ + 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(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, [stats.commander]?", + ) + 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', 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." + +/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!"), \ + 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(datum/wargame_unit_stats/stats) + var/list/lines = list( + "All mounts local control, watch fire for friendlies!", + "[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 new file mode 100644 index 00000000000000..94fb5ef4f0f039 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/bombs.dm @@ -0,0 +1,58 @@ +/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 = 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_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]!"), \ + blind_message = span_warning("[firer] lets loose a volley of rockets at [target]!")) + +/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!", + "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 = 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_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!"), \ + 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_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!", + "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..cdf3b071b7c0e3 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/missiles.dm @@ -0,0 +1,141 @@ +/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', 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]!"), \ + 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. \ + Max target range of [attack_range] tiles. [action_point_cost] AP to fire." + +/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) + +/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/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!"), \ + 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" + attack_roll = "5d4+2" + damage_roll_bonus = -2 + evadable = FALSE + radial_icon_state = "weapon_missile_small" + action_point_cost = 1 + maximum_ammo = 6 + missile_type = /obj/structure/wargame_hologram/controllable/swarm_missile + +/datum/wargame_weapon/missile/swarm/strike + maximum_ammo = 2 + +/datum/wargame_weapon/missile/swarm/weapon_firing_sound(obj/firer) + 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. \ + 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]!"), \ + 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(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 [stats.commander].", + ) + 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/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!"), \ + blind_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" + 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/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) + +/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/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]!"), \ + blind_message = span_warning("[firer] dispenses a large torpedo, which slowly spins up and gimbals toward [target]!")) + +/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 [stats.commander].", + "Good track confirmed, torpedo dispensed [stats.commander].", + ) + 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." + +/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]!"), \ + 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 new file mode 100644 index 00000000000000..6d198f097e85b5 --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/ramming.dm @@ -0,0 +1,24 @@ +/datum/wargame_weapon/ramming/strike + attack_roll = "5d5+2" + damage_roll_bonus = 0 + evadable = TRUE + small_ship_disadvantage = TRUE + 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( + "One. Last. Plan!", + "It's been an honor [stats.commander].", + "Initiating burn drive, godspeed [stats.commander].", + "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!"), \ + 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 new file mode 100644 index 00000000000000..4657d37ece339b --- /dev/null +++ b/modular_doppler/wargaming/code/unit_weapons/small_guns.dm @@ -0,0 +1,53 @@ +/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/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." + +/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!"), \ + 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(datum/wargame_unit_stats/stats) + var/list/lines = list( + "Tracking looks good, let's let 'em have some.", + "Autocannon belts spooled, ready to fire [stats.commander].", + "Like to see them dodge this one, shells away [stats.commander].", + ) + return pick(lines) + +/datum/wargame_weapon/pdc + weapon_name = "26mm PDC" + attack_roll = "1d12+6" + damage_roll_bonus = -3 + 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/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." + +/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!"), \ + 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(datum/wargame_unit_stats/stats) + var/list/lines = list( + "We need PDC on that target, now!", + "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 new file mode 100644 index 00000000000000..f2c3bfdc391750 Binary files /dev/null and b/modular_doppler/wargaming/icons/actions.dmi differ diff --git a/modular_doppler/wargaming/icons/holograms.dmi b/modular_doppler/wargaming/icons/holograms.dmi new file mode 100644 index 00000000000000..7679aadedf805f Binary files /dev/null and b/modular_doppler/wargaming/icons/holograms.dmi differ diff --git a/modular_doppler/wargaming/icons/items.dmi b/modular_doppler/wargaming/icons/items.dmi new file mode 100644 index 00000000000000..5bcfe3fb11fb03 Binary files /dev/null and b/modular_doppler/wargaming/icons/items.dmi differ diff --git a/modular_doppler/wargaming/icons/mob/lefthand.dmi b/modular_doppler/wargaming/icons/mob/lefthand.dmi new file mode 100644 index 00000000000000..b927250496ef9e Binary files /dev/null and b/modular_doppler/wargaming/icons/mob/lefthand.dmi differ diff --git a/modular_doppler/wargaming/icons/mob/righthand.dmi b/modular_doppler/wargaming/icons/mob/righthand.dmi new file mode 100644 index 00000000000000..71f000d300048d Binary files /dev/null and b/modular_doppler/wargaming/icons/mob/righthand.dmi differ diff --git a/modular_doppler/wargaming/icons/mob/worn.dmi b/modular_doppler/wargaming/icons/mob/worn.dmi new file mode 100644 index 00000000000000..494aee0e50a926 Binary files /dev/null and b/modular_doppler/wargaming/icons/mob/worn.dmi differ diff --git a/modular_doppler/wargaming/icons/projectors_and_holograms.dmi b/modular_doppler/wargaming/icons/projectors_and_holograms.dmi deleted file mode 100644 index 569b6d7274c9b5..00000000000000 Binary files a/modular_doppler/wargaming/icons/projectors_and_holograms.dmi and /dev/null differ diff --git a/modular_doppler/wargaming/icons/turf.dmi b/modular_doppler/wargaming/icons/turf.dmi new file mode 100644 index 00000000000000..d1fb9d9a8bba32 Binary files /dev/null and b/modular_doppler/wargaming/icons/turf.dmi differ diff --git a/modular_doppler/wargaming/sound/attributions.txt b/modular_doppler/wargaming/sound/attributions.txt new file mode 100644 index 00000000000000..ac9e33fc7e438a --- /dev/null +++ b/modular_doppler/wargaming/sound/attributions.txt @@ -0,0 +1,8 @@ +All of the sounds listed are genuinely just generated with tones, waves, or noise with various fun patterns on them. +autocannon.ogg +beam.ogg +cannon.ogg +missile_launch.ogg +ship_explode.ogg +ship_hit.ogg +swarm_launch.ogg diff --git a/modular_doppler/wargaming/sound/autocannon.ogg b/modular_doppler/wargaming/sound/autocannon.ogg new file mode 100644 index 00000000000000..a1b62c3e956f57 Binary files /dev/null and b/modular_doppler/wargaming/sound/autocannon.ogg differ diff --git a/modular_doppler/wargaming/sound/autocannonlong.ogg b/modular_doppler/wargaming/sound/autocannonlong.ogg new file mode 100644 index 00000000000000..f57538a9a2eb1c Binary files /dev/null and b/modular_doppler/wargaming/sound/autocannonlong.ogg differ diff --git a/modular_doppler/wargaming/sound/beam.ogg b/modular_doppler/wargaming/sound/beam.ogg new file mode 100644 index 00000000000000..def94ad0b28693 Binary files /dev/null and b/modular_doppler/wargaming/sound/beam.ogg differ diff --git a/modular_doppler/wargaming/sound/cannon.ogg b/modular_doppler/wargaming/sound/cannon.ogg new file mode 100644 index 00000000000000..b7860bae334ec1 Binary files /dev/null and b/modular_doppler/wargaming/sound/cannon.ogg differ diff --git a/modular_doppler/wargaming/sound/missile_launch.ogg b/modular_doppler/wargaming/sound/missile_launch.ogg new file mode 100644 index 00000000000000..e67fb556078fc1 Binary files /dev/null and b/modular_doppler/wargaming/sound/missile_launch.ogg differ diff --git a/modular_doppler/wargaming/sound/ship_explode.ogg b/modular_doppler/wargaming/sound/ship_explode.ogg new file mode 100644 index 00000000000000..9cf5d7df788708 Binary files /dev/null and b/modular_doppler/wargaming/sound/ship_explode.ogg differ diff --git a/modular_doppler/wargaming/sound/ship_hit.ogg b/modular_doppler/wargaming/sound/ship_hit.ogg new file mode 100644 index 00000000000000..a355a1e77894be Binary files /dev/null and b/modular_doppler/wargaming/sound/ship_hit.ogg differ diff --git a/modular_doppler/wargaming/sound/swarm_launch.ogg b/modular_doppler/wargaming/sound/swarm_launch.ogg new file mode 100644 index 00000000000000..ebfb4e87169df1 Binary files /dev/null and b/modular_doppler/wargaming/sound/swarm_launch.ogg differ 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", 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" + ] +} diff --git a/tgstation.dme b/tgstation.dme index dd28a59c785235..2e614c4d43b20f 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,35 @@ #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\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" +#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