diff --git a/code/__DEFINES/battlepass.dm b/code/__DEFINES/battlepass.dm new file mode 100644 index 000000000000..7a02f087dca3 --- /dev/null +++ b/code/__DEFINES/battlepass.dm @@ -0,0 +1,14 @@ +#define CHALLENGE_NONE "none" +#define CHALLENGE_XENO "Xeno Challenge" +#define CHALLENGE_HUMAN "Human Challenge" + +#define REWARD_CATEGORY_ARMOR "armor" +#define REWARD_CATEGORY_HELMET_FIRE "helmet_fire" +#define REWARD_CATEGORY_PARTICLE "particle" +#define REWARD_CATEGORY_TOY "toy" +#define REWARD_CATEGORY_OVERLAY "overlay" +#define REWARD_CATEGORY_SHOTGUN_RESKIN "shotgun_reskin" +#define REWARD_CATEGORY_M41A_RESKIN "m41a_reskin" + +/// How long do you need to play/stay alive for to earn the game-end points +#define BATTLEPASS_TIME_TO_EARN_REWARD (0.1 MINUTES) diff --git a/code/__DEFINES/dcs/signals/atom/mob/living/signals_human.dm b/code/__DEFINES/dcs/signals/atom/mob/living/signals_human.dm index 2e247cdccc73..42d793a7da8c 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/living/signals_human.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/living/signals_human.dm @@ -70,3 +70,5 @@ /// From /obj/item/proc/dig_out_shrapnel() : () #define COMSIG_HUMAN_SHRAPNEL_REMOVED "human_shrapnel_removed" + +#define COMSIG_HUMAN_USED_DEFIB "human_used_defib" diff --git a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm index e6e1e64e9c7e..bd973424f8a4 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm @@ -78,3 +78,13 @@ /// From /mob/living/carbon/xenomorph/proc/hivemind_talk(): (message) #define COMSIG_XENO_TRY_HIVEMIND_TALK "xeno_try_hivemind_talk" #define COMPONENT_OVERRIDE_HIVEMIND_TALK (1<<0) + +#define COMSIG_XENO_RAGE_MAX "xeno_rage_max" + +#define COMSIG_XENO_PLANTED_FRUIT "xeno_planted_fruit" + +#define COMSIG_XENO_FACEHUGGED_HUMAN "xeno_facehugged_human" + +#define COMSIG_XENO_FTH_MAX_ACID "xeno_fth_max_acid" + +#define COMSIG_FIRER_PROJECTILE_DIRECT_HIT "firer_projectile_direct_hit" diff --git a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm index f4df347c62db..222505284640 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm @@ -185,3 +185,5 @@ #define COMSIG_MOB_END_TUTORIAL "mob_end_tutorial" #define COMSIG_MOB_NESTED "mob_nested" + +#define COMSIG_MOB_KILL_TOTAL_INCREASED "mob_kill_total_increased" diff --git a/code/__DEFINES/dcs/signals/signals_datum.dm b/code/__DEFINES/dcs/signals/signals_datum.dm index b798d510763e..2e3c50a93d48 100644 --- a/code/__DEFINES/dcs/signals/signals_datum.dm +++ b/code/__DEFINES/dcs/signals/signals_datum.dm @@ -67,3 +67,5 @@ /// Fired on the lazy template datum when the template is finished loading. (list/loaded_atom_movables, list/loaded_turfs, list/loaded_areas) #define COMSIG_LAZY_TEMPLATE_LOADED "lazy_template_loaded" + +#define COMSIG_BATTLEPASS_CHALLENGE_COMPLETED "battlepass_challenge_completed" diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index ee958d87f580..3ef58afe1f1a 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -165,6 +165,8 @@ #define FULLSCREEN_VULTURE_SCOPE_LAYER 17.21 /// in critical #define FULLSCREEN_CRIT_LAYER 17.25 +/// tier-up battlepass +#define FULLSCREEN_BATTLEPASS_TIERUP 18 #define HUD_LAYER 19 #define ABOVE_HUD_LAYER 20 diff --git a/code/__DEFINES/particle.dm b/code/__DEFINES/particle.dm new file mode 100644 index 000000000000..5657566a63bb --- /dev/null +++ b/code/__DEFINES/particle.dm @@ -0,0 +1,5 @@ +// /obj/effect/abstract/particle_holder/var/particle_flags +// Flags that effect how a particle holder displays something + +/// If we're inside something inside a mob, display off that mob too +#define PARTICLE_ATTACH_MOB (1<<0) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 47aa0e732c76..cb3cfba34a59 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -149,6 +149,7 @@ #define SS_INIT_STICKY -30 #define SS_INIT_PREDSHIPS -31 #define SS_INIT_OBJECTIVES -32 +#define SS_INIT_BATTLEPASS -33 #define SS_INIT_MINIMAP -34 #define SS_INIT_STATPANELS -98 #define SS_INIT_CHAT -100 //Should be last to ensure chat remains smooth during init. diff --git a/code/__HELPERS/string_lists.dm b/code/__HELPERS/string_lists.dm index 076bbf642756..3a5fa5256021 100644 --- a/code/__HELPERS/string_lists.dm +++ b/code/__HELPERS/string_lists.dm @@ -1,4 +1,5 @@ GLOBAL_LIST_EMPTY(string_lists) +#define json_load(FILE) (json_decode(file2text(FILE))) /** * Caches lists with non-numeric stringify-able values (text or typepath). diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm index 0bd2206091ba..d63ecf9e2dd1 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -179,6 +179,11 @@ icon_state = "vulture_scope_overlay_spotter" should_resize = FALSE +/atom/movable/screen/fullscreen/battlepass + icon_state = "battlepass_tierup" + layer = FULLSCREEN_BATTLEPASS_TIERUP + show_when_dead = TRUE + //Weather overlays// /atom/movable/screen/fullscreen/weather diff --git a/code/controllers/subsystem/battlepass.dm b/code/controllers/subsystem/battlepass.dm new file mode 100644 index 000000000000..9f3a0c133df6 --- /dev/null +++ b/code/controllers/subsystem/battlepass.dm @@ -0,0 +1,160 @@ +SUBSYSTEM_DEF(battlepass) + name = "Battlepass" + flags = SS_NO_FIRE + init_order = SS_INIT_BATTLEPASS + /// The maximum tier the battlepass goes to + var/maximum_tier = 20 + /// What season we're currently in + var/season = 1 + /// Dict of all marine challenges to their pick weight + var/list/marine_challenges = list() + /// Dict of all xeno challenges to their pick weight + var/list/xeno_challenges = list() + /// List of paths of all battlepass rewards for the current season, in order + var/list/season_rewards = list() + /// List of all paths of all premium battlepass rewards for the current season, in order + var/list/premium_season_rewards = list() + var/list/marine_battlepass_earners = list() + var/list/xeno_battlepass_earners = list() + +/datum/controller/subsystem/battlepass/Initialize() + if(!fexists("config/battlepass.json")) + return + + var/list/battlepass_data = json_load("config/battlepass.json") + + maximum_tier = battlepass_data["maximum_tier"] + season = battlepass_data["season"] + + for(var/reward_path in battlepass_data["reward_data"]) + season_rewards += text2path(reward_path) + + for(var/reward_path in battlepass_data["premium_reward_data"]) + premium_season_rewards += text2path(reward_path) + + for(var/datum/battlepass_challenge/challenge_path as anything in subtypesof(/datum/battlepass_challenge)) + switch(initial(challenge_path.challenge_category)) + if(CHALLENGE_NONE) + continue + + if(CHALLENGE_HUMAN) + marine_challenges[challenge_path] = initial(challenge_path.pick_weight) + + if(CHALLENGE_XENO) + xeno_challenges[challenge_path] = initial(challenge_path.pick_weight) + + RegisterSignal(src, COMSIG_SUBSYSTEM_POST_INITIALIZE, PROC_REF(do_postinit)) + + for(var/client/client as anything in GLOB.clients) + if(!client.owned_battlepass) + continue + + client.owned_battlepass.verify_rewards() + + return SS_INIT_SUCCESS + +/datum/controller/subsystem/battlepass/proc/do_postinit(datum/source) + SIGNAL_HANDLER + + UnregisterSignal(src, COMSIG_SUBSYSTEM_POST_INITIALIZE) + + for(var/client/client as anything in GLOB.clients) + client.owned_battlepass?.check_daily_challenge_reset() + +/// Returns a typepath of a challenge of the given category +/datum/controller/subsystem/battlepass/proc/get_challenge(challenge_type = CHALLENGE_NONE) + switch(challenge_type) + if(CHALLENGE_NONE) + return + + if(CHALLENGE_HUMAN) + return pick_weight(marine_challenges) + + if(CHALLENGE_XENO) + return pick_weight(xeno_challenges) + + +/datum/controller/subsystem/battlepass/proc/give_sides_points(marine_points = 0, xeno_points = 0) + if(marine_points) + give_side_points(marine_points, marine_battlepass_earners) + + if(xeno_points) + give_side_points(xeno_points, xeno_battlepass_earners) + +/datum/controller/subsystem/battlepass/proc/save_battlepasses() + for(var/client/player as anything in GLOB.clients) + player.save_battlepass() + +/datum/controller/subsystem/battlepass/proc/give_side_points(point_amount = 0, ckey_list) + if(!islist(ckey_list)) + CRASH("give_side_points in SSbattlepass called without giving a list of ckeys") + + for(var/ckey in ckey_list) + if(ckey in GLOB.directory) + var/client/ckey_client = GLOB.directory[ckey] + if(ckey_client.owned_battlepass) + ckey_client.owned_battlepass.add_xp(point_amount) + else + if(!fexists("data/player_saves/[copytext(ckey,1,2)]/[ckey]/battlepass.sav")) + continue + + var/savefile/ckey_save = new("data/player_saves/[copytext(ckey,1,2)]/[ckey]/battlepass.sav") + + ckey_save["xp"] += point_amount // if they're >=10 XP, it'll get sorted next time they log on + +/// Proc meant for admin calling to see BP levels of all online players +/datum/controller/subsystem/battlepass/proc/output_bp_levels(mob/caller) + var/list/levels = list( + "1" = 0, + "2" = 0, + "3" = 0, + "4" = 0, + "5" = 0, + "6" = 0, + "7" = 0, + "8" = 0, + "9" = 0, + "10" = 0, + "11" = 0, + "12" = 0, + "13" = 0, + "14" = 0, + "15" = 0, + "16" = 0, + "17" = 0, + "18" = 0, + "19" = 0, + "20" = 0, + ) + for(var/client/player_client as anything in GLOB.clients) + if(!player_client.owned_battlepass) + continue + + levels["[player_client.owned_battlepass.tier]"] += 1 + + to_chat(caller, SPAN_NOTICE(json_encode(levels))) + + +/datum/controller/subsystem/battlepass/proc/get_bp_ge_to_tier(mob/caller, tiernum = 1) + var/i = 0 + for(var/a in flist("data/player_saves/")) + for(var/ckey_str in flist("data/player_saves/[a]/")) + if(!fexists("data/player_saves/[a]/[ckey_str]/battlepass.sav")) + continue + + var/savefile/save_obj = new("data/player_saves/[a]/[ckey_str]/battlepass.sav") + if(save_obj["tier"] >= tiernum) + i++ + to_chat(caller, SPAN_NOTICE("[i]")) + + +/datum/controller/subsystem/battlepass/proc/get_bp_xp_total(mob/caller) + var/xp = 0 + for(var/a in flist("data/player_saves/")) + for(var/ckey_str in flist("data/player_saves/[a]/")) + if(!fexists("data/player_saves/[a]/[ckey_str]/battlepass.sav")) + continue + + var/savefile/save_obj = new("data/player_saves/[a]/[ckey_str]/battlepass.sav") + xp += (((save_obj["tier"] - 1) * 10) + save_obj["xp"]) + to_chat(caller, SPAN_NOTICE("[xp]")) diff --git a/code/datums/statistics/entities/death_stats.dm b/code/datums/statistics/entities/death_stats.dm index 76e3605c157f..575f84d46d20 100644 --- a/code/datums/statistics/entities/death_stats.dm +++ b/code/datums/statistics/entities/death_stats.dm @@ -111,6 +111,7 @@ if(cause_mob) cause_mob.life_kills_total += life_value + SEND_SIGNAL(cause_mob, COMSIG_MOB_KILL_TOTAL_INCREASED, src, cause_data) if(getBruteLoss()) new_death.total_brute = round(getBruteLoss()) diff --git a/code/game/gamemodes/cm_initialize.dm b/code/game/gamemodes/cm_initialize.dm index 971eb8f07178..1f5ab604fdaf 100644 --- a/code/game/gamemodes/cm_initialize.dm +++ b/code/game/gamemodes/cm_initialize.dm @@ -510,6 +510,7 @@ Additional game mode variables. to_chat(xeno_candidate, SPAN_WARNING("You are banished from this hive, You may not rejoin unless the Queen re-admits you or dies.")) return FALSE if(transfer_xeno(xeno_candidate, new_xeno)) + SSbattlepass.xeno_battlepass_earners |= new_xeno?.client?.ckey return TRUE to_chat(xeno_candidate, "JAS01: Something went wrong, tell a coder.") diff --git a/code/game/gamemodes/colonialmarines/colonialmarines.dm b/code/game/gamemodes/colonialmarines/colonialmarines.dm index a66403fc00f5..8503d2e9f535 100644 --- a/code/game/gamemodes/colonialmarines/colonialmarines.dm +++ b/code/game/gamemodes/colonialmarines/colonialmarines.dm @@ -359,12 +359,14 @@ if(GLOB.round_statistics && GLOB.round_statistics.current_map) GLOB.round_statistics.current_map.total_xeno_victories++ GLOB.round_statistics.current_map.total_xeno_majors++ + SSbattlepass.give_sides_points(3, 5) if(MODE_INFESTATION_M_MAJOR) musical_track = pick('sound/theme/winning_triumph1.ogg','sound/theme/winning_triumph2.ogg') end_icon = "marine_major" if(GLOB.round_statistics && GLOB.round_statistics.current_map) GLOB.round_statistics.current_map.total_marine_victories++ GLOB.round_statistics.current_map.total_marine_majors++ + SSbattlepass.give_sides_points(5, 3) if(MODE_INFESTATION_X_MINOR) var/list/living_player_list = count_humans_and_xenos(get_affected_zlevels()) if(living_player_list[1] && !living_player_list[2]) // If Xeno Minor but Xenos are dead and Humans are alive, see which faction is the last standing @@ -389,16 +391,19 @@ end_icon = "xeno_minor" if(GLOB.round_statistics && GLOB.round_statistics.current_map) GLOB.round_statistics.current_map.total_xeno_victories++ + SSbattlepass.give_sides_points(3, 4) if(MODE_INFESTATION_M_MINOR) musical_track = pick('sound/theme/neutral_hopeful1.ogg','sound/theme/neutral_hopeful2.ogg') end_icon = "marine_minor" if(GLOB.round_statistics && GLOB.round_statistics.current_map) GLOB.round_statistics.current_map.total_marine_victories++ + SSbattlepass.give_sides_points(4, 3) if(MODE_INFESTATION_DRAW_DEATH) end_icon = "draw" musical_track = 'sound/theme/neutral_hopeful2.ogg' if(GLOB.round_statistics && GLOB.round_statistics.current_map) GLOB.round_statistics.current_map.total_draws++ + SSbattlepass.give_sides_points(3, 3) var/sound/S = sound(musical_track, channel = SOUND_CHANNEL_LOBBY) S.status = SOUND_STREAM sound_to(world, S) @@ -418,6 +423,7 @@ declare_completion_announce_predators() declare_completion_announce_medal_awards() declare_fun_facts() + SSbattlepass.save_battlepasses() add_current_round_status_to_end_results("Round End") diff --git a/code/game/jobs/job/antag/xeno/xenomorph.dm b/code/game/jobs/job/antag/xeno/xenomorph.dm index 78b6ab7e3ab2..7cea0073b0a8 100644 --- a/code/game/jobs/job/antag/xeno/xenomorph.dm +++ b/code/game/jobs/job/antag/xeno/xenomorph.dm @@ -11,6 +11,7 @@ // a proper limit. spawn_positions = -1 total_positions = -1 + xeno_sided = TRUE /datum/job/antag/xenos/proc/calculate_extra_spawn_positions(count) return max((round(count * XENO_TO_TOTAL_SPAWN_RATIO)), 0) diff --git a/code/game/jobs/job/civilians/civilian.dm b/code/game/jobs/job/civilians/civilian.dm index a9631f2ed9c1..e2169dd42d13 100644 --- a/code/game/jobs/job/civilians/civilian.dm +++ b/code/game/jobs/job/civilians/civilian.dm @@ -1,5 +1,6 @@ /datum/job/civilian gear_preset = /datum/equipment_preset/colonist + marine_sided = TRUE /datum/timelock/medic name = "Medical Roles" diff --git a/code/game/jobs/job/command/command.dm b/code/game/jobs/job/command/command.dm index d430352d6e83..60d895d46ac5 100644 --- a/code/game/jobs/job/command/command.dm +++ b/code/game/jobs/job/command/command.dm @@ -3,6 +3,7 @@ supervisors = "the acting commanding officer" total_positions = 1 spawn_positions = 1 + marine_sided = TRUE /datum/timelock/command name = "Command Roles" @@ -23,7 +24,7 @@ /datum/timelock/human/can_play(client/C) return C.get_total_human_playtime() >= time_required - + /datum/timelock/human/get_role_requirement(client/C) return time_required - C.get_total_human_playtime() @@ -33,4 +34,4 @@ /datum/timelock/dropship/New(name, time_required, list/roles) . = ..() src.roles = JOB_DROPSHIP_ROLES_LIST - + diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm index 1a04c3cafeb5..7af3f98a4916 100644 --- a/code/game/jobs/job/job.dm +++ b/code/game/jobs/job/job.dm @@ -39,6 +39,8 @@ var/job_options /// If TRUE, this job will spawn w/ a cryo emergency kit during evac/red alert var/gets_emergency_kit = TRUE + var/marine_sided = FALSE + var/xeno_sided = FALSE /datum/job/New() . = ..() @@ -239,8 +241,22 @@ setup_human(new_character, NP) + addtimer(CALLBACK(src, PROC_REF(add_to_battlepass_earners), new_character), BATTLEPASS_TIME_TO_EARN_REWARD) + return new_character +/datum/job/proc/add_to_battlepass_earners(mob/living/carbon/human/character) + if(!character?.client?.ckey) + return + + var/ckey = character.client.ckey + + // You cannot double dip; marine or xeno only + if(marine_sided && !(ckey in SSbattlepass.xeno_battlepass_earners)) + SSbattlepass.marine_battlepass_earners |= ckey + else if(xeno_sided && !(ckey in SSbattlepass.marine_battlepass_earners)) + SSbattlepass.xeno_battlepass_earners |= ckey + /datum/job/proc/equip_job(mob/living/M) if(!istype(M)) return diff --git a/code/game/jobs/job/marine/marine.dm b/code/game/jobs/job/marine/marine.dm index e07c1edd3138..ff1c5178b74c 100644 --- a/code/game/jobs/job/marine/marine.dm +++ b/code/game/jobs/job/marine/marine.dm @@ -4,6 +4,7 @@ total_positions = 8 spawn_positions = 8 allow_additional = 1 + marine_sided = TRUE /datum/job/marine/generate_entry_message(mob/living/carbon/human/current_human) if(current_human.assigned_squad) diff --git a/code/game/jobs/job/special/uscm.dm b/code/game/jobs/job/special/uscm.dm index 2308c5af2961..74c369c63493 100644 --- a/code/game/jobs/job/special/uscm.dm +++ b/code/game/jobs/job/special/uscm.dm @@ -1,3 +1,6 @@ +/datum/job/special/uscm + marine_sided = TRUE + /datum/job/special/uscm/colonel title = JOB_COLONEL /datum/job/special/uscm/general diff --git a/code/game/objects/effects/decals/crayon.dm b/code/game/objects/effects/decals/crayon.dm index 35e354c121bb..85687525d54e 100644 --- a/code/game/objects/effects/decals/crayon.dm +++ b/code/game/objects/effects/decals/crayon.dm @@ -28,3 +28,11 @@ overlays += shadeOverlay add_hiddenprint(usr) + +/obj/effect/decal/cleanable/sus_crayon + name = "suspicious rune" + desc = "A rune drawn in crayon." + icon = 'code/modules/battlepass/rewards/sprites/toy.dmi' + icon_state = "sus_crayon_rune" + layer = ABOVE_TURF_LAYER + anchored = TRUE diff --git a/code/game/objects/items/devices/defibrillator.dm b/code/game/objects/items/devices/defibrillator.dm index bbeb2046aff0..be39cc4e21b3 100644 --- a/code/game/objects/items/devices/defibrillator.dm +++ b/code/game/objects/items/devices/defibrillator.dm @@ -230,6 +230,7 @@ playsound(get_turf(src), 'sound/items/defib_success.ogg', 25, 0) user.track_life_saved(user.job) user.life_revives_total++ + SEND_SIGNAL(user, COMSIG_HUMAN_USED_DEFIB, H) H.handle_revive() if(heart) heart.take_damage(rand(min_heart_damage_dealt, max_heart_damage_dealt), TRUE) // Make death and revival leave lasting consequences diff --git a/code/game/objects/items/toys/crayons.dm b/code/game/objects/items/toys/crayons.dm index 1d9e2e1a4d54..607eb1e6ddf6 100644 --- a/code/game/objects/items/toys/crayons.dm +++ b/code/game/objects/items/toys/crayons.dm @@ -101,3 +101,38 @@ qdel(src) else ..() + + +/obj/item/toy/suspicious + name = "suspicious crayon" + desc = "There's something off about this crayon..." + icon = 'code/modules/battlepass/rewards/sprites/toy.dmi' + icon_state = "sus_crayon" + var/uses = 10 + +/obj/item/toy/suspicious/afterattack(atom/target, mob/user, proximity) + if(!proximity) + return + + if(istype(target, /turf/open/floor)) + if(do_after(user, 5 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC)) + new /obj/effect/decal/cleanable/sus_crayon(target) + to_chat(user, SPAN_NOTICE("You finish drawing.")) + target.add_fingerprint(user) // Adds their fingerprints to the floor the crayon is drawn on. + if(uses) + uses-- + if(!uses) + to_chat(user, SPAN_DANGER("You used up your crayon!")) + qdel(src) + +/obj/item/toy/suspicious/attack(mob/M as mob, mob/user as mob) + if(M == user) + to_chat(user, SPAN_NOTICE("You take a bite of the crayon and swallow it.")) + user.nutrition += 5 + if(uses) + uses -= 5 + if(uses <= 0) + to_chat(user, SPAN_DANGER("You ate your crayon!")) + qdel(src) + else + ..() diff --git a/code/game/objects/items/toys/toys.dm b/code/game/objects/items/toys/toys.dm index 91d8164dcf38..09d81be3d637 100644 --- a/code/game/objects/items/toys/toys.dm +++ b/code/game/objects/items/toys/toys.dm @@ -660,3 +660,21 @@ /obj/item/toy/plush/shark/alt icon_state = "shark_alt" + +/obj/item/toy/plush/runner_toy + name = "runner toy" + desc = "A squishy, shrunken rendition of an XX-121 runner caste. It feels comforting, though you're not sure why anyone would make these." + icon = 'code/modules/battlepass/rewards/sprites/toy.dmi' + icon_state = "runner_toy" + +/obj/item/toy/plush/warrior_toy + name = "warrior toy" + desc = "A squishy, shrunken rendition of an XX-121 warrior caste. It feels comforting, though you're not sure why anyone would make these." + icon = 'code/modules/battlepass/rewards/sprites/toy.dmi' + icon_state = "warrior_toy" + +/obj/item/toy/plush/queen_toy + name = "queen toy" + desc = "A squishy, shrunken rendition of an XX-121 queen. It feels comforting, though you're not sure why anyone would make these." + icon = 'code/modules/battlepass/rewards/sprites/toy.dmi' + icon_state = "queen_toy" diff --git a/code/game/world.dm b/code/game/world.dm index e55741ca71e5..12b41d01d6a1 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -219,6 +219,7 @@ GLOBAL_LIST_INIT(reboot_sfx, file2list("config/reboot_sfx.txt")) return json_encode(response) /world/Reboot(reason) + SSbattlepass.save_battlepasses() Master.Shutdown() send_reboot_sound() var/server = CONFIG_GET(string/server) diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index 0b27cf268a12..2587f0fb27f7 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -391,6 +391,24 @@ ..() +/datum/asset/spritesheet/battlepass + name = "battlepass" + +/datum/asset/spritesheet/battlepass/register() + var/list/iconstates_added = list() + for(var/datum/battlepass_reward/reward as anything in subtypesof(/datum/battlepass_reward)) + reward = new reward + if(reward.icon_state in iconstates_added) + qdel(reward) + continue + + var/icon/sprite = icon(reward.icon, reward.icon_state) + sprite.Scale(96, 96) + Insert(reward.icon_state, sprite) + iconstates_added += reward.icon_state + qdel(reward) + return ..() + /datum/asset/spritesheet/gun_lineart_modes name = "gunlineartmodes" diff --git a/code/modules/battlepass/battlepass.dm b/code/modules/battlepass/battlepass.dm new file mode 100644 index 000000000000..ada4481c25aa --- /dev/null +++ b/code/modules/battlepass/battlepass.dm @@ -0,0 +1,297 @@ +/mob/verb/battlepass() + set category = "OOC" + set name = "Battlepass" + + if(!client) + return + + if(!SSbattlepass.initialized) + return + + client.owned_battlepass?.ui_interact(src) + +/mob/living/carbon/verb/claim_battlepass_reward() + set category = "OOC" + set name = "Claim Battlepass Reward" + + if(!client) + return + + var/list/acceptable_rewards = list() + for(var/datum/battlepass_reward/reward as anything in client.owned_battlepass.rewards) + if(reward.can_claim(src)) + acceptable_rewards += reward + + if(!length(acceptable_rewards)) + to_chat(src, SPAN_WARNING("You have no rewards to claim.")) + return + + var/datum/battlepass_reward/chosen_reward = tgui_input_list(src, "Claim a battlepass reward.", "Claim Reward", acceptable_rewards) + if(!chosen_reward || !chosen_reward.can_claim(src)) + return + + if(chosen_reward.on_claim(src)) + claimed_reward_categories |= chosen_reward.category + +/mob/var/obj/effect/abstract/particle_holder/particle_holder + +/// Each client possesses an instanced /datum/battlepass +/datum/battlepass + /// The current battlepass tier the user is at + /// Max tier is stored on the master battlepass the server owns + var/tier = 1 as num + + /// How much XP the user has in the current tier + var/xp = 0 as num + + /// How much XP you need to go up a tier + var/xp_tierup = 10 as num + + // If the user has paid for a premium battlepass + //var/premium = FALSE // (: + + /// List of personal daily challenges + var/list/datum/battlepass_challenge/daily_challenges = list() + + /// When challenges were last updated, formatted as a UNIX timestamp + var/daily_challenges_last_updated = 0 as num + + /// Weakref to the owning client + var/datum/weakref/owning_client + + /// All earned battlepass reward instances + var/list/datum/battlepass_reward/rewards = list() + + /// Typepaths of all earned battlepass rewards. This isn't saved because it's populated by loading the rewards list + var/list/reward_paths = list() + + /// The tier of the battlepass the last time on_tier_up() was called + var/previous_on_tier_up_tier = 0 + +/datum/battlepass/proc/add_xp(xp_amount) + if(tier >= SSbattlepass.maximum_tier) + return + + xp += xp_amount + check_tier_up(TRUE) + +/datum/battlepass/proc/check_tier_up(display_popup = TRUE) + if(xp >= xp_tierup) + var/tier_increase = round(xp / xp_tierup) + xp -= (tier_increase * xp_tierup) + tier += tier_increase + on_tier_up(display_popup) + update_static_data_for_all_viewers() + +/datum/battlepass/proc/on_tier_up(display_popup = TRUE) + if(previous_on_tier_up_tier == tier) + return + + for(var/i in previous_on_tier_up_tier + 1 to tier) + var/reward_path = SSbattlepass.season_rewards[i] + var/datum/battlepass_reward/reward = new reward_path + rewards += reward + reward_paths += reward_path + + if(display_popup) + display_tier_up_popup() + + var/list/types_in_rewards = list() + for(var/datum/battlepass_reward/reward as anything in rewards) + if(reward.type in types_in_rewards) + rewards -= reward + reward_paths -= reward.type + qdel(reward) + continue + + types_in_rewards += reward.type + + previous_on_tier_up_tier = tier + var/client/oc = owning_client.resolve() + log_game("[oc.mob] ([oc.key]) has increased to battlepass tier [tier]") + +/datum/battlepass/proc/display_tier_up_popup() + if(!owning_client) + return + + var/client/user_client = owning_client.resolve() + if(!user_client.mob) + return + + playsound_client(user_client, 'sound/effects/bp_levelup.mp3', get_turf(user_client.mob), 70, FALSE) // .mp3, sue me + user_client.mob.overlay_fullscreen("battlepass_tierup", /atom/movable/screen/fullscreen/battlepass) + addtimer(CALLBACK(user_client.mob, TYPE_PROC_REF(/mob, clear_fullscreen), "battlepass_tierup", 0), 1.2 SECONDS) + +/// Check that the user has all the rewards they should (in case rewards shifted in config or etc). +/// Doesn't remove ones that aren't in their tiers (in case they have some from a previous season, for example) +/datum/battlepass/proc/verify_rewards() + for(var/i in 1 to tier) + var/reward_path = SSbattlepass.season_rewards[i] + if(reward_path in reward_paths) + continue + + rewards += new reward_path + reward_paths += reward_path + +/// Check if it's been 24h since daily challenges were last assigned +/datum/battlepass/proc/check_daily_challenge_reset() + // Clients can connect before the SS is initialized + if(!SSbattlepass?.initialized) + return + + // 86400 seconds (24*60^2) is one day + if((daily_challenges_last_updated + (24 * 60 * 60)) <= rustg_unix_timestamp()) + reset_daily_challenges() + return TRUE + return FALSE + +/// Give the battlepass a new set of daily challenges +/datum/battlepass/proc/reset_daily_challenges() + if(!owning_client) + return + + // We give the player 2 marine challenges and 2 xeno challenges + QDEL_LIST(daily_challenges) + + for(var/i in 1 to 2) + var/gotten_path = SSbattlepass.get_challenge(CHALLENGE_HUMAN) + var/datum/battlepass_challenge/human_challenge = new gotten_path(owning_client.resolve()) + RegisterSignal(human_challenge, COMSIG_BATTLEPASS_CHALLENGE_COMPLETED, PROC_REF(on_challenge_complete)) + daily_challenges += human_challenge + + for(var/i in 1 to 2) + var/gotten_path = SSbattlepass.get_challenge(CHALLENGE_XENO) + var/datum/battlepass_challenge/xeno_challenge = new gotten_path(owning_client.resolve()) + RegisterSignal(xeno_challenge, COMSIG_BATTLEPASS_CHALLENGE_COMPLETED, PROC_REF(on_challenge_complete)) + daily_challenges += xeno_challenge + + daily_challenges_last_updated = rustg_unix_timestamp() + +/// Returns a list of all daily challenges formatted for a savefile +/datum/battlepass/proc/serialize_daily_challenges() + . = list() + for(var/datum/battlepass_challenge/challenge as anything in daily_challenges) + . += list(challenge.serialize()) + +/datum/battlepass/proc/serialize_rewards() + . = list() + var/list/saved_reward_paths = list() + for(var/datum/battlepass_reward/reward as anything in rewards) + if(reward.type in saved_reward_paths) + continue + + . += reward.type + saved_reward_paths += reward.type + +/// Provided a list of lists for daily challenges, load daily challenges from the lists +/datum/battlepass/proc/load_daily_challenges(list/challenge_data) + if(!owning_client) + return + + for(var/list/entry as anything in challenge_data) + if(!("type" in entry)) + continue + + var/path = entry["type"] + var/datum/battlepass_challenge/challenge = new path(owning_client.resolve()) + daily_challenges += challenge + RegisterSignal(challenge, COMSIG_BATTLEPASS_CHALLENGE_COMPLETED, PROC_REF(on_challenge_complete)) + challenge.deserialize(entry) + +/datum/battlepass/proc/load_rewards(list/reward_data) + var/list/loaded_paths = list() + for(var/path in reward_data) + if(path in loaded_paths) + continue + + var/datum/battlepass_reward/reward = new path + rewards += reward + reward_paths += path + loaded_paths += path + +/// Called whenever a challenge is completed +/datum/battlepass/proc/on_challenge_complete(datum/battlepass_challenge/challenge) + SIGNAL_HANDLER + + if(!owning_client) + return + + var/client/resolved_client = owning_client.resolve() + challenge.completed = TRUE + add_xp(challenge.completion_xp) + challenge.unhook_signals(resolved_client.mob) + +/datum/battlepass/proc/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Battlepass") + ui.open() + +/datum/battlepass/ui_state(mob/user) + return GLOB.always_state + +/datum/battlepass/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/spritesheet/battlepass), + ) + +/datum/battlepass/ui_data(mob/user) + var/list/data = list() + + data["tier"] = tier + data["xp"] = tier >= SSbattlepass.maximum_tier ? xp_tierup : xp + data["xp_tierup"] = xp_tierup + + return data + +/datum/battlepass/ui_static_data(mob/user) + var/list/data = list() + + data["season"] = SSbattlepass.season + data["max_tier"] = SSbattlepass.maximum_tier + + data["rewards"] = list() + + var/i = 1 + for(var/datum/battlepass_reward/reward_path as anything in SSbattlepass.season_rewards) + data["rewards"] += list(list( + "name" = initial(reward_path.name), + "icon_state" = initial(reward_path.icon_state), + "tier" = i, + "lifeform_type" = initial(reward_path.lifeform_type), + )) + i++ + + data["premium_rewards"] = list() + + i = 1 + for(var/datum/battlepass_reward/reward_path as anything in SSbattlepass.premium_season_rewards) + data["premium_rewards"] += list(list( + "name" = initial(reward_path.name), + "icon_state" = initial(reward_path.icon_state), + "tier" = i, + "lifeform_type" = initial(reward_path.lifeform_type), + )) + i++ + + data["daily_challenges"] = list() + + for(var/datum/battlepass_challenge/daily_challenge as anything in daily_challenges) + data["daily_challenges"] += list(list( + "name" = daily_challenge.name, + "desc" = daily_challenge.desc, + "completed" = daily_challenge.completed, + "category" = daily_challenge.challenge_category, + "completion_xp" = daily_challenge.completion_xp, + "completion_percent" = daily_challenge.get_completion_percent(), + "completion_numerator" = daily_challenge.get_completion_numerator(), + "completion_denominator" = daily_challenge.get_completion_denominator(), + )) + + return data + +/datum/battlepass/vv_edit_var(var_name, var_value) + if(usr.ckey != "zonespace") + to_chat(usr, SPAN_BOLDWARNING("FUCK OFF")) + return FALSE + return ..() diff --git a/code/modules/battlepass/battlepass_client.dm b/code/modules/battlepass/battlepass_client.dm new file mode 100644 index 000000000000..13800f978fce --- /dev/null +++ b/code/modules/battlepass/battlepass_client.dm @@ -0,0 +1,50 @@ +/client + /// Reference to the client's battlepass + var/datum/battlepass/owned_battlepass + +/client/New() + . = ..() + init_battlepass() + +/client/Destroy() + save_battlepass() + return ..() + +/client/proc/init_battlepass() + if(fexists("data/player_saves/[copytext(ckey,1,2)]/[ckey]/battlepass.sav")) + load_battlepass() + return + + owned_battlepass = new() + owned_battlepass.owning_client = WEAKREF(src) + owned_battlepass.tier = 1 + owned_battlepass.xp = 0 + owned_battlepass.daily_challenges_last_updated = 0 + owned_battlepass.check_daily_challenge_reset() + owned_battlepass.previous_on_tier_up_tier = owned_battlepass.tier + +/client/proc/load_battlepass() + var/savefile/battlepass_save = new("data/player_saves/[copytext(ckey,1,2)]/[ckey]/battlepass.sav") + + owned_battlepass = new() + owned_battlepass.owning_client = WEAKREF(src) + owned_battlepass.tier = battlepass_save["tier"] + owned_battlepass.xp = battlepass_save["xp"] + owned_battlepass.check_tier_up(FALSE) + owned_battlepass.daily_challenges_last_updated = battlepass_save["daily_challenges_last_updated"] + owned_battlepass.load_daily_challenges(battlepass_save["daily_challenges"]) + owned_battlepass.check_daily_challenge_reset() + owned_battlepass.load_rewards(battlepass_save["rewards"]) + owned_battlepass.previous_on_tier_up_tier = battlepass_save["previous_on_tier_up_tier"] + if(SSbattlepass.initialized) + owned_battlepass.verify_rewards() + +/client/proc/save_battlepass() + var/savefile/battlepass_save = new("data/player_saves/[copytext(ckey,1,2)]/[ckey]/battlepass.sav") + + battlepass_save["tier"] = owned_battlepass.tier + battlepass_save["xp"] = owned_battlepass.xp + battlepass_save["daily_challenges_last_updated"] = owned_battlepass.daily_challenges_last_updated + battlepass_save["daily_challenges"] = owned_battlepass.serialize_daily_challenges() + battlepass_save["rewards"] = owned_battlepass.serialize_rewards() + battlepass_save["previous_on_tier_up_tier"] = owned_battlepass.previous_on_tier_up_tier diff --git a/code/modules/battlepass/challenges/_battlepass_challenge.dm b/code/modules/battlepass/challenges/_battlepass_challenge.dm new file mode 100644 index 000000000000..a6b520cf865a --- /dev/null +++ b/code/modules/battlepass/challenges/_battlepass_challenge.dm @@ -0,0 +1,87 @@ +/datum/battlepass_challenge + /// What this challenge is called + var/name = "" as text + /// The description that is given to the user on what the challenge is and how to complete it + var/desc = "" as text + /// If this challenge has been completed + var/completed = FALSE as num + /// How much XP this challenge gives on completion + var/completion_xp = 0 as num + /// If this is a xeno or marine-focused challenge + var/challenge_category = CHALLENGE_NONE as text + /// How much weight this challenge has in its category + var/pick_weight = 1 as num + +/datum/battlepass_challenge/New(client/owning_client) + . = ..() + if(!owning_client) + return FALSE + + RegisterSignal(owning_client, COMSIG_CLIENT_MOB_LOGGED_IN, PROC_REF(on_client_login_mob)) + return TRUE + +/// Override to change the desc of the challenge post-init +/datum/battlepass_challenge/proc/regenerate_desc() + return + +/// Override this to add behavior to any mob connected to the owning client +/datum/battlepass_challenge/proc/on_client_login_mob(datum/source, mob/logged_in_mob) + SIGNAL_HANDLER + SHOULD_CALL_PARENT(TRUE) + + UnregisterSignal(logged_in_mob.client, COMSIG_CLIENT_MOB_LOGGED_IN) + RegisterSignal(logged_in_mob, COMSIG_MOB_LOGOUT, PROC_REF(on_client_logout_mob)) + +/// Cleanup code from on_client_login_mob +/datum/battlepass_challenge/proc/on_client_logout_mob(mob/source) + SIGNAL_HANDLER + SHOULD_CALL_PARENT(TRUE) + + unhook_signals(source) + UnregisterSignal(source, COMSIG_MOB_LOGOUT) + if(source.logging_ckey in GLOB.directory) + RegisterSignal(GLOB.directory[source.logging_ckey], COMSIG_CLIENT_MOB_LOGGED_IN, PROC_REF(on_client_login_mob)) + +/datum/battlepass_challenge/proc/unhook_signals(mob/source) + return + +/// Override to return true/false depending on the challenge's completion +/datum/battlepass_challenge/proc/check_challenge_completed() + return TRUE + +/// Do things if the challenge is completed, will do nothing if it is not +/datum/battlepass_challenge/proc/on_possible_challenge_completed() + if(!check_challenge_completed()) + return FALSE + SEND_SIGNAL(src, COMSIG_BATTLEPASS_CHALLENGE_COMPLETED) + return TRUE + +/// Get how completed the challenge is as a percentage out of 1 +/datum/battlepass_challenge/proc/get_completion_percent() + return 0 + +/datum/battlepass_challenge/proc/get_completion_numerator() + return 0 + +/datum/battlepass_challenge/proc/get_completion_denominator() + return 1 + +/// Convert data about this challenge into a list to be inserted in a savefile +/datum/battlepass_challenge/proc/serialize() + SHOULD_CALL_PARENT(TRUE) + return list( + "type" = type, + "name" = name, + "desc" = desc, + "completion_xp" = completion_xp, + "completed" = completed + ) + +/// Given a list, update the challenge data accordingly +/datum/battlepass_challenge/proc/deserialize(list/save_list) + SHOULD_CALL_PARENT(TRUE) + name = save_list["name"] + desc = save_list["desc"] + completion_xp = save_list["completion_xp"] + completed = save_list["completed"] + diff --git a/code/modules/battlepass/challenges/human_misc/defib_players.dm b/code/modules/battlepass/challenges/human_misc/defib_players.dm new file mode 100644 index 000000000000..c9513d9467f3 --- /dev/null +++ b/code/modules/battlepass/challenges/human_misc/defib_players.dm @@ -0,0 +1,60 @@ +/datum/battlepass_challenge/defib_players + name = "Defibrillate Players" + desc = "Successfully defibrillate AMOUNT unique marine players." + challenge_category = CHALLENGE_HUMAN + completion_xp = 5 + var/minimum = 4 as num + var/maximum = 8 as num + var/requirement = 0 as num + var/list/mob_name_list = list() + + +/datum/battlepass_challenge/defib_players/New(client/owning_client) + . = ..() + if(!.) + return . + + requirement = rand(minimum, maximum) + regenerate_desc() + +/datum/battlepass_challenge/defib_players/regenerate_desc() + desc = "Successfully defibrillate [requirement] unique marine player\s." + +/datum/battlepass_challenge/defib_players/on_client_login_mob(datum/source, mob/logged_in_mob) + . = ..() + if(!completed) + RegisterSignal(logged_in_mob, COMSIG_HUMAN_USED_DEFIB, PROC_REF(on_defib)) + +/datum/battlepass_challenge/defib_players/unhook_signals(mob/source) + UnregisterSignal(source, COMSIG_HUMAN_USED_DEFIB) + +/datum/battlepass_challenge/defib_players/check_challenge_completed() + return (length(mob_name_list) >= requirement) + +/datum/battlepass_challenge/defib_players/get_completion_percent() + return (length(mob_name_list) / requirement) + +/datum/battlepass_challenge/defib_players/get_completion_numerator() + return length(mob_name_list) + +/datum/battlepass_challenge/defib_players/get_completion_denominator() + return requirement + +/datum/battlepass_challenge/defib_players/serialize() + . = ..() + .["requirement"] = requirement + .["filled"] = mob_name_list + +/datum/battlepass_challenge/defib_players/deserialize(list/save_list) + . = ..() + requirement = save_list["requirement"] + mob_name_list = save_list["filled"] + +/// When the xeno plants a resin node +/datum/battlepass_challenge/defib_players/proc/on_defib(datum/source, mob/living/carbon/human/defibbed) + SIGNAL_HANDLER + + mob_name_list |= defibbed.real_name + on_possible_challenge_completed() + + diff --git a/code/modules/battlepass/challenges/kill/_kill_enemies.dm b/code/modules/battlepass/challenges/kill/_kill_enemies.dm new file mode 100644 index 000000000000..fa7cba32bd07 --- /dev/null +++ b/code/modules/battlepass/challenges/kill/_kill_enemies.dm @@ -0,0 +1,60 @@ +/datum/battlepass_challenge/kill_enemies + /// How many enemies need to be killed to complete the challenge + var/enemy_kills_required = 0 as num + /// How many enemies have been killed thus far for this challenge + var/current_enemy_kills = 0 as num + /// A list of valid mob paths to count towards kills + var/list/valid_kill_paths = list() + /// The minimum amt of kills possibly required to complete this challenge + var/kill_requirement_lower = 0 as num + /// The maximum amt of kills possibly required to complete this challenge + var/kill_requirement_upper = 0 as num + +/datum/battlepass_challenge/kill_enemies/New(client/owning_client) + . = ..() + if(!.) + return . + + enemy_kills_required = rand(kill_requirement_lower, kill_requirement_upper) + regenerate_desc() + +/datum/battlepass_challenge/kill_enemies/on_client_login_mob(datum/source, mob/logged_in_mob) + . = ..() + if(!completed) + RegisterSignal(logged_in_mob, COMSIG_MOB_KILL_TOTAL_INCREASED, PROC_REF(on_kill)) + +/datum/battlepass_challenge/kill_enemies/unhook_signals(mob/source) + UnregisterSignal(source, COMSIG_MOB_KILL_TOTAL_INCREASED) + +/datum/battlepass_challenge/kill_enemies/check_challenge_completed() + return (enemy_kills_required <= current_enemy_kills) + +/datum/battlepass_challenge/kill_enemies/get_completion_percent() + return (current_enemy_kills / enemy_kills_required) + +/datum/battlepass_challenge/kill_enemies/get_completion_numerator() + return current_enemy_kills + +/datum/battlepass_challenge/kill_enemies/get_completion_denominator() + return enemy_kills_required + +/datum/battlepass_challenge/kill_enemies/serialize() + . = ..() + .["enemy_kills_required"] = enemy_kills_required + .["current_enemy_kills"] = current_enemy_kills + .["valid_kill_paths"] = valid_kill_paths + +/datum/battlepass_challenge/kill_enemies/deserialize(list/save_list) + . = ..() + enemy_kills_required = save_list["enemy_kills_required"] + current_enemy_kills = save_list["current_enemy_kills"] + valid_kill_paths = save_list["valid_kill_paths"] + +/datum/battlepass_challenge/kill_enemies/proc/on_kill(mob/source, mob/killed_mob, datum/cause_data/cause_data) + SIGNAL_HANDLER + + // Facehuggers and lessers have a life_value of 0, so they aren't counted + if(is_type_in_list(killed_mob, valid_kill_paths) && killed_mob.life_value) + current_enemy_kills++ + + on_possible_challenge_completed() diff --git a/code/modules/battlepass/challenges/kill/kill_humans.dm b/code/modules/battlepass/challenges/kill/kill_humans.dm new file mode 100644 index 000000000000..a383fb152c22 --- /dev/null +++ b/code/modules/battlepass/challenges/kill/kill_humans.dm @@ -0,0 +1,29 @@ +/datum/battlepass_challenge/kill_enemies/humans + name = "Kill Humans" + desc = "Kill AMOUNT humans as a Xenomorph." + challenge_category = CHALLENGE_XENO + kill_requirement_lower = 3 + kill_requirement_upper = 5 + valid_kill_paths = list( + /mob/living/carbon/human, + ) + completion_xp = 6 + pick_weight = 10 + +/datum/battlepass_challenge/kill_enemies/humans/regenerate_desc() + desc = "Kill [enemy_kills_required] human\s as a Xenomorph." + +/datum/battlepass_challenge/kill_enemies/humans/on_kill(mob/source, mob/killed_mob, datum/cause_data/cause_data) + if(!isxeno(source) || (source.faction == killed_mob.faction)) + return + + if(!ishuman(killed_mob)) + return + + var/mob/living/carbon/human/killed_human = killed_mob + + // Synths, preds, etc. don't count towards this + if(!isspecieshuman(killed_human)) + return + + return ..() diff --git a/code/modules/battlepass/challenges/kill/kill_xenomorphs.dm b/code/modules/battlepass/challenges/kill/kill_xenomorphs.dm new file mode 100644 index 000000000000..c72753143632 --- /dev/null +++ b/code/modules/battlepass/challenges/kill/kill_xenomorphs.dm @@ -0,0 +1,19 @@ +/datum/battlepass_challenge/kill_enemies/xenomorphs + name = "Kill Xenomorphs" + desc = "Kill AMOUNT Xenomorphs as a human." + challenge_category = CHALLENGE_HUMAN + kill_requirement_lower = 2 + kill_requirement_upper = 3 + valid_kill_paths = list( + /mob/living/carbon/xenomorph, + ) + completion_xp = 6 + +/datum/battlepass_challenge/kill_enemies/xenomorphs/regenerate_desc() + desc = "Kill [enemy_kills_required] Xenomorph\s as a human." + +/datum/battlepass_challenge/kill_enemies/xenomorphs/on_kill(mob/source, mob/killed_mob, datum/cause_data/cause_data) + if(!ishuman(source) || (source.faction == killed_mob.faction)) + return + + return ..() diff --git a/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/_kill_caste.dm b/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/_kill_caste.dm new file mode 100644 index 000000000000..3b8c0fff8b47 --- /dev/null +++ b/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/_kill_caste.dm @@ -0,0 +1,18 @@ +/datum/battlepass_challenge/kill_enemies/xenomorphs/caste + name = "Kill Xenomorphs - Caste" + desc = "Kill AMOUNT CASTEs as a human." + challenge_category = CHALLENGE_NONE + /// Possible xenoes to pick from + var/list/possible_xeno_castes = list() + +/datum/battlepass_challenge/kill_enemies/xenomorphs/caste/New(client/owning_client) + . = ..() + if(!.) + return . + + valid_kill_paths = list(pick(possible_xeno_castes)) + regenerate_desc() + +/datum/battlepass_challenge/kill_enemies/xenomorphs/caste/regenerate_desc() + var/mob/xeno_path = valid_kill_paths[1] + desc = "Kill [enemy_kills_required] [initial(xeno_path.name)]\s as a human." diff --git a/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/kill_t1.dm b/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/kill_t1.dm new file mode 100644 index 000000000000..79c87d39b471 --- /dev/null +++ b/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/kill_t1.dm @@ -0,0 +1,9 @@ +/datum/battlepass_challenge/kill_enemies/xenomorphs/caste/t1 + possible_xeno_castes = list( + /mob/living/carbon/xenomorph/drone, + /mob/living/carbon/xenomorph/runner, + /mob/living/carbon/xenomorph/defender, + /mob/living/carbon/xenomorph/sentinel, + ) + pick_weight = 7 + challenge_category = CHALLENGE_HUMAN diff --git a/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/kill_t2.dm b/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/kill_t2.dm new file mode 100644 index 000000000000..4243ece10cef --- /dev/null +++ b/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/kill_t2.dm @@ -0,0 +1,13 @@ +/datum/battlepass_challenge/kill_enemies/xenomorphs/caste/t2 + possible_xeno_castes = list( + /mob/living/carbon/xenomorph/carrier, + /mob/living/carbon/xenomorph/burrower, + /mob/living/carbon/xenomorph/hivelord, + /mob/living/carbon/xenomorph/warrior, + /mob/living/carbon/xenomorph/spitter, + /mob/living/carbon/xenomorph/lurker, + ) + kill_requirement_lower = 1 + kill_requirement_upper = 2 + pick_weight = 7 + challenge_category = CHALLENGE_HUMAN diff --git a/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/kill_t3.dm b/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/kill_t3.dm new file mode 100644 index 000000000000..6830e8273714 --- /dev/null +++ b/code/modules/battlepass/challenges/kill/specific/kill_xeno_caste/kill_t3.dm @@ -0,0 +1,11 @@ +/datum/battlepass_challenge/kill_enemies/xenomorphs/caste/t3 + possible_xeno_castes = list( + /mob/living/carbon/xenomorph/crusher, + /mob/living/carbon/xenomorph/praetorian, + /mob/living/carbon/xenomorph/ravager, + /mob/living/carbon/xenomorph/boiler, + ) + kill_requirement_lower = 1 + kill_requirement_upper = 1 + pick_weight = 6 + challenge_category = CHALLENGE_HUMAN diff --git a/code/modules/battlepass/challenges/kill/specific/weapons/_kill_weapon.dm b/code/modules/battlepass/challenges/kill/specific/weapons/_kill_weapon.dm new file mode 100644 index 000000000000..1757f73d7dad --- /dev/null +++ b/code/modules/battlepass/challenges/kill/specific/weapons/_kill_weapon.dm @@ -0,0 +1,35 @@ +/datum/battlepass_challenge/kill_enemies/xenomorphs/weapon + name = "Kill Xenomorphs - Weapon" + desc = "Kill AMOUNT Xenomorphs using a WEAPON." + kill_requirement_lower = 1 + kill_requirement_upper = 2 + challenge_category = CHALLENGE_NONE + /// A list of possible weapons for this challenge to choose from. I would do it with typepaths but cause data only tracks names + var/list/possible_weapons = list() + /// The weapon chosen for this challenge + var/weapon_to_use = "" as text + +/datum/battlepass_challenge/kill_enemies/xenomorphs/weapon/New(client/owning_client) + . = ..() + if(!.) + return . + + weapon_to_use = pick(possible_weapons) + regenerate_desc() + +/datum/battlepass_challenge/kill_enemies/xenomorphs/weapon/regenerate_desc() + desc = "Kill [enemy_kills_required] Xenomorph\s using \an [weapon_to_use]." + +/datum/battlepass_challenge/kill_enemies/xenomorphs/weapon/on_kill(mob/source, mob/killed_mob, datum/cause_data/cause_data) + if(!findtext(cause_data.cause_name, weapon_to_use)) + return + + return ..() + +/datum/battlepass_challenge/kill_enemies/xenomorphs/weapon/serialize() + . = ..() + .["weapon_to_use"] = weapon_to_use + +/datum/battlepass_challenge/kill_enemies/xenomorphs/weapon/deserialize(list/save_list) + . = ..() + weapon_to_use = save_list["weapon_to_use"] diff --git a/code/modules/battlepass/challenges/kill/specific/weapons/common_weapons.dm b/code/modules/battlepass/challenges/kill/specific/weapons/common_weapons.dm new file mode 100644 index 000000000000..76e378615252 --- /dev/null +++ b/code/modules/battlepass/challenges/kill/specific/weapons/common_weapons.dm @@ -0,0 +1,8 @@ +/datum/battlepass_challenge/kill_enemies/xenomorphs/weapon/common + possible_weapons = list( + "M39 submachinegun", + "M4RA battle rifle", + "M41A pulse rifle MK2", + "M37A2 pump shotgun", + ) + challenge_category = CHALLENGE_HUMAN diff --git a/code/modules/battlepass/challenges/kill/specific/weapons/pistol_weapons.dm b/code/modules/battlepass/challenges/kill/specific/weapons/pistol_weapons.dm new file mode 100644 index 000000000000..348ba2b81593 --- /dev/null +++ b/code/modules/battlepass/challenges/kill/specific/weapons/pistol_weapons.dm @@ -0,0 +1,11 @@ +/datum/battlepass_challenge/kill_enemies/xenomorphs/weapon/pistol + possible_weapons = list( + "VP78 pistol", + "SU-6 Smartpistol", + "M44 combat revolver", + "88 Mod 4 combat pistol", + "M4A3 service pistol", + ) + kill_requirement_upper = 1 + completion_xp = 7 // pistols are tough + challenge_category = CHALLENGE_HUMAN diff --git a/code/modules/battlepass/challenges/kill/specific/weapons/req_weapons.dm b/code/modules/battlepass/challenges/kill/specific/weapons/req_weapons.dm new file mode 100644 index 000000000000..0f14493abd84 --- /dev/null +++ b/code/modules/battlepass/challenges/kill/specific/weapons/req_weapons.dm @@ -0,0 +1,9 @@ +/datum/battlepass_challenge/kill_enemies/xenomorphs/weapon/req + possible_weapons = list( + "M240A1 incinerator unit", + "MOU53 break action shotgun", + "XM88 heavy rifle", + "M41A pulse rifle", + "M41AE2 heavy pulse rifle", + ) + challenge_category = CHALLENGE_HUMAN diff --git a/code/modules/battlepass/challenges/xeno_misc/berserker_empower.dm b/code/modules/battlepass/challenges/xeno_misc/berserker_empower.dm new file mode 100644 index 000000000000..32f583526927 --- /dev/null +++ b/code/modules/battlepass/challenges/xeno_misc/berserker_empower.dm @@ -0,0 +1,62 @@ +/datum/battlepass_challenge/berserker_rage + name = "Max Berserker Rage" + desc = "As a Berserker Ravager, enter maximum berserker rage AMOUNT times." + challenge_category = CHALLENGE_XENO + completion_xp = 5 + pick_weight = 6 + /// The minimum possible amount of times rage needs to be entered + var/minimum_rages = 2 as num + /// The maximum + var/maximum_rages = 4 as num + var/rage_requirement = 0 as num + var/completed_rages = 0 as num + +/datum/battlepass_challenge/berserker_rage/New(client/owning_client) + . = ..() + if(!.) + return . + + rage_requirement = rand(minimum_rages, maximum_rages) + regenerate_desc() + +/datum/battlepass_challenge/berserker_rage/regenerate_desc() + desc = "As a Berserker Ravager, enter maximum berserker rage [rage_requirement] time\s." + +/datum/battlepass_challenge/berserker_rage/on_client_login_mob(datum/source, mob/logged_in_mob) + . = ..() + if(!completed) + RegisterSignal(logged_in_mob, COMSIG_XENO_RAGE_MAX, PROC_REF(on_rage_max)) + +/datum/battlepass_challenge/berserker_rage/unhook_signals(mob/source) + UnregisterSignal(source, COMSIG_XENO_RAGE_MAX) + +/datum/battlepass_challenge/berserker_rage/check_challenge_completed() + return (completed_rages >= rage_requirement) + +/datum/battlepass_challenge/berserker_rage/get_completion_percent() + return (completed_rages / rage_requirement) + +/datum/battlepass_challenge/berserker_rage/get_completion_numerator() + return completed_rages + +/datum/battlepass_challenge/berserker_rage/get_completion_denominator() + return rage_requirement + +/datum/battlepass_challenge/berserker_rage/serialize() + . = ..() + .["rage_requirement"] = rage_requirement + .["completed_rages"] = completed_rages + +/datum/battlepass_challenge/berserker_rage/deserialize(list/save_list) + . = ..() + rage_requirement = save_list["rage_requirement"] + completed_rages = save_list["completed_rages"] + +/// When the xeno plants a resin node +/datum/battlepass_challenge/berserker_rage/proc/on_rage_max(datum/source) + SIGNAL_HANDLER + + completed_rages++ + on_possible_challenge_completed() + + diff --git a/code/modules/battlepass/challenges/xeno_misc/facehug.dm b/code/modules/battlepass/challenges/xeno_misc/facehug.dm new file mode 100644 index 000000000000..135f35f21af7 --- /dev/null +++ b/code/modules/battlepass/challenges/xeno_misc/facehug.dm @@ -0,0 +1,60 @@ +/datum/battlepass_challenge/facehug + name = "Facehug Humans" + desc = "As a facehugger, facehug AMOUNT humans." + challenge_category = CHALLENGE_XENO + completion_xp = 5 + pick_weight = 7 + var/minimum = 1 as num + var/maximum = 3 as num + var/requirement = 0 as num + var/filled = 0 as num + +/datum/battlepass_challenge/facehug/New(client/owning_client) + . = ..() + if(!.) + return . + + requirement = rand(minimum, maximum) + regenerate_desc() + +/datum/battlepass_challenge/facehug/regenerate_desc() + desc = "As a facehugger, facehug [requirement] human\s." + +/datum/battlepass_challenge/facehug/on_client_login_mob(datum/source, mob/logged_in_mob) + . = ..() + if(!completed) + RegisterSignal(logged_in_mob, COMSIG_XENO_FACEHUGGED_HUMAN, PROC_REF(on_sigtrigger)) + +/datum/battlepass_challenge/facehug/unhook_signals(mob/source) + UnregisterSignal(source, COMSIG_XENO_FACEHUGGED_HUMAN) + +/datum/battlepass_challenge/facehug/check_challenge_completed() + return (filled >= requirement) + +/datum/battlepass_challenge/facehug/get_completion_percent() + return (filled / requirement) + +/datum/battlepass_challenge/facehug/get_completion_numerator() + return filled + +/datum/battlepass_challenge/facehug/get_completion_denominator() + return requirement + +/datum/battlepass_challenge/facehug/serialize() + . = ..() + .["requirement"] = requirement + .["filled"] = filled + +/datum/battlepass_challenge/facehug/deserialize(list/save_list) + . = ..() + requirement = save_list["requirement"] + filled = save_list["filled"] + +/// When the xeno plants a resin node +/datum/battlepass_challenge/facehug/proc/on_sigtrigger(datum/source) + SIGNAL_HANDLER + + filled++ + on_possible_challenge_completed() + + diff --git a/code/modules/battlepass/challenges/xeno_misc/for_the_hive.dm b/code/modules/battlepass/challenges/xeno_misc/for_the_hive.dm new file mode 100644 index 000000000000..66e47188ab65 --- /dev/null +++ b/code/modules/battlepass/challenges/xeno_misc/for_the_hive.dm @@ -0,0 +1,60 @@ +/datum/battlepass_challenge/for_the_hive + name = "For The Hive!" + desc = "As an Acider Runner, detonate For The Hive at maximum acid AMOUNT times." + challenge_category = CHALLENGE_XENO + completion_xp = 6 + pick_weight = 6 + var/minimum = 1 as num + var/maximum = 2 as num + var/requirement = 0 as num + var/filled = 0 as num + +/datum/battlepass_challenge/for_the_hive/New(client/owning_client) + . = ..() + if(!.) + return . + + requirement = rand(minimum, maximum) + regenerate_desc() + +/datum/battlepass_challenge/for_the_hive/regenerate_desc() + desc = "As an Acider Runner, detonate For The Hive at maximum acid [requirement] times." + +/datum/battlepass_challenge/for_the_hive/on_client_login_mob(datum/source, mob/logged_in_mob) + . = ..() + if(!completed) + RegisterSignal(logged_in_mob, COMSIG_XENO_FTH_MAX_ACID, PROC_REF(on_sigtrigger)) + +/datum/battlepass_challenge/for_the_hive/unhook_signals(mob/source) + UnregisterSignal(source, COMSIG_XENO_FTH_MAX_ACID) + +/datum/battlepass_challenge/for_the_hive/check_challenge_completed() + return (filled >= requirement) + +/datum/battlepass_challenge/for_the_hive/get_completion_percent() + return (filled / requirement) + +/datum/battlepass_challenge/for_the_hive/get_completion_numerator() + return filled + +/datum/battlepass_challenge/for_the_hive/get_completion_denominator() + return requirement + +/datum/battlepass_challenge/for_the_hive/serialize() + . = ..() + .["requirement"] = requirement + .["filled"] = filled + +/datum/battlepass_challenge/for_the_hive/deserialize(list/save_list) + . = ..() + requirement = save_list["requirement"] + filled = save_list["filled"] + +/// When the xeno plants a resin node +/datum/battlepass_challenge/for_the_hive/proc/on_sigtrigger(datum/source) + SIGNAL_HANDLER + + filled++ + on_possible_challenge_completed() + + diff --git a/code/modules/battlepass/challenges/xeno_misc/glob_direct_hit.dm b/code/modules/battlepass/challenges/xeno_misc/glob_direct_hit.dm new file mode 100644 index 000000000000..b49d5f9a99c0 --- /dev/null +++ b/code/modules/battlepass/challenges/xeno_misc/glob_direct_hit.dm @@ -0,0 +1,62 @@ +/datum/battlepass_challenge/glob_hits + name = "Direct Glob Hits" + desc = "Land AMOUNT direct acid glob hits as a Boiler." + challenge_category = CHALLENGE_XENO + completion_xp = 6 + pick_weight = 6 + var/minimum = 1 as num + var/maximum = 4 as num + var/requirement = 0 as num + var/filled = 0 as num + +/datum/battlepass_challenge/glob_hits/New(client/owning_client) + . = ..() + if(!.) + return . + + requirement = rand(minimum, maximum) + regenerate_desc() + +/datum/battlepass_challenge/glob_hits/regenerate_desc() + desc = "Land [requirement] direct acid glob hit\s as a Boiler." + +/datum/battlepass_challenge/glob_hits/on_client_login_mob(datum/source, mob/logged_in_mob) + . = ..() + if(!completed) + RegisterSignal(logged_in_mob, COMSIG_FIRER_PROJECTILE_DIRECT_HIT, PROC_REF(on_sigtrigger)) + +/datum/battlepass_challenge/glob_hits/unhook_signals(mob/source) + UnregisterSignal(source, COMSIG_FIRER_PROJECTILE_DIRECT_HIT) + +/datum/battlepass_challenge/glob_hits/check_challenge_completed() + return (filled >= requirement) + +/datum/battlepass_challenge/glob_hits/get_completion_percent() + return (filled / requirement) + +/datum/battlepass_challenge/glob_hits/get_completion_numerator() + return filled + +/datum/battlepass_challenge/glob_hits/get_completion_denominator() + return requirement + +/datum/battlepass_challenge/glob_hits/serialize() + . = ..() + .["requirement"] = requirement + .["filled"] = filled + +/datum/battlepass_challenge/glob_hits/deserialize(list/save_list) + . = ..() + requirement = save_list["requirement"] + filled = save_list["filled"] + +/// When the xeno plants a resin node +/datum/battlepass_challenge/glob_hits/proc/on_sigtrigger(datum/source, obj/projectile/hit_projectile) + SIGNAL_HANDLER + if(!istype(hit_projectile.ammo, /datum/ammo/xeno/boiler_gas)) + return + + filled++ + on_possible_challenge_completed() + + diff --git a/code/modules/battlepass/challenges/xeno_misc/plant_froot.dm b/code/modules/battlepass/challenges/xeno_misc/plant_froot.dm new file mode 100644 index 000000000000..30caa69c091a --- /dev/null +++ b/code/modules/battlepass/challenges/xeno_misc/plant_froot.dm @@ -0,0 +1,62 @@ +/datum/battlepass_challenge/plant_fruit + name = "Plant Resin Fruit" + desc = "Plant AMOUNT resin fruits." + challenge_category = CHALLENGE_XENO + completion_xp = 5 + pick_weight = 8 + var/minimum = 20 as num + var/maximum = 30 as num + var/requirement = 0 as num + var/filled = 0 as num + +/datum/battlepass_challenge/plant_fruit/New(client/owning_client) + . = ..() + if(!.) + return . + + requirement = rand(minimum, maximum) + regenerate_desc() + +/datum/battlepass_challenge/plant_fruit/regenerate_desc() + desc = "Plant [requirement] resin fruit\s." + +/datum/battlepass_challenge/plant_fruit/on_client_login_mob(datum/source, mob/logged_in_mob) + . = ..() + if(!completed) + RegisterSignal(logged_in_mob, COMSIG_XENO_PLANTED_FRUIT, PROC_REF(on_sigtrigger)) + +/datum/battlepass_challenge/plant_fruit/unhook_signals(mob/source) + UnregisterSignal(source, COMSIG_XENO_PLANTED_FRUIT) + +/datum/battlepass_challenge/plant_fruit/check_challenge_completed() + return (filled >= requirement) + +/datum/battlepass_challenge/plant_fruit/get_completion_percent() + return (filled / requirement) + +/datum/battlepass_challenge/plant_fruit/get_completion_numerator() + return filled + +/datum/battlepass_challenge/plant_fruit/get_completion_denominator() + return requirement + +/datum/battlepass_challenge/plant_fruit/serialize() + . = ..() + .["requirement"] = requirement + .["filled"] = filled + +/datum/battlepass_challenge/plant_fruit/deserialize(list/save_list) + . = ..() + requirement = save_list["requirement"] + filled = save_list["filled"] + +/// When the xeno plants a resin node +/datum/battlepass_challenge/plant_fruit/proc/on_sigtrigger(datum/source, mob/planter) + SIGNAL_HANDLER + if(should_block_game_interaction(planter)) + return + + filled++ + on_possible_challenge_completed() + + diff --git a/code/modules/battlepass/challenges/xeno_misc/plant_resin_nodes.dm b/code/modules/battlepass/challenges/xeno_misc/plant_resin_nodes.dm new file mode 100644 index 000000000000..dcb14a8ea153 --- /dev/null +++ b/code/modules/battlepass/challenges/xeno_misc/plant_resin_nodes.dm @@ -0,0 +1,66 @@ +/datum/battlepass_challenge/plant_resin_nodes + name = "Plant Resin Nodes" + desc = "Plant AMOUNT resin nodes." + challenge_category = CHALLENGE_XENO + completion_xp = 5 + pick_weight = 8 + /// The minimum possible amount of nodes that need to be planted + var/minimum_nodes = 20 as num + /// The maximum + var/maximum_nodes = 30 as num + /// How many nodes need to be planted + var/node_requirement = 0 as num + /// How many nodes have been planted so far + var/planted_nodes = 0 as num + +/datum/battlepass_challenge/plant_resin_nodes/New(client/owning_client) + . = ..() + if(!.) + return . + + node_requirement = rand(minimum_nodes, maximum_nodes) + regenerate_desc() + +/datum/battlepass_challenge/plant_resin_nodes/regenerate_desc() + desc = "Plant [node_requirement] resin node\s." + +/datum/battlepass_challenge/plant_resin_nodes/on_client_login_mob(datum/source, mob/logged_in_mob) + . = ..() + if(!completed) + RegisterSignal(logged_in_mob, COMSIG_XENO_PLANT_RESIN_NODE, PROC_REF(on_plant_node)) + +/datum/battlepass_challenge/plant_resin_nodes/unhook_signals(mob/source) + UnregisterSignal(source, COMSIG_XENO_PLANT_RESIN_NODE) + +/datum/battlepass_challenge/plant_resin_nodes/check_challenge_completed() + return (planted_nodes >= node_requirement) + +/datum/battlepass_challenge/plant_resin_nodes/get_completion_percent() + return (planted_nodes / node_requirement) + +/datum/battlepass_challenge/plant_resin_nodes/get_completion_numerator() + return planted_nodes + +/datum/battlepass_challenge/plant_resin_nodes/get_completion_denominator() + return node_requirement + +/datum/battlepass_challenge/plant_resin_nodes/serialize() + . = ..() + .["node_requirement"] = node_requirement + .["planted_nodes"] = planted_nodes + +/datum/battlepass_challenge/plant_resin_nodes/deserialize(list/save_list) + . = ..() + node_requirement = save_list["node_requirement"] + planted_nodes = save_list["planted_nodes"] + +/// When the xeno plants a resin node +/datum/battlepass_challenge/plant_resin_nodes/proc/on_plant_node(datum/source, mob/planter) + SIGNAL_HANDLER + if(should_block_game_interaction(planter)) + return + + planted_nodes++ + on_possible_challenge_completed() + + diff --git a/code/modules/battlepass/rewards/_battlepass_reward.dm b/code/modules/battlepass/rewards/_battlepass_reward.dm new file mode 100644 index 000000000000..b691116c13a1 --- /dev/null +++ b/code/modules/battlepass/rewards/_battlepass_reward.dm @@ -0,0 +1,34 @@ +/mob/living/carbon + var/list/claimed_reward_categories = list() + +/datum/battlepass_reward + /// The name of this reward + var/name = "" as text + /// The iconfile that contains the image of this reward + var/icon = 'code/modules/battlepass/rewards/sprites/battlepass.dmi' + /// The iconstate of the image of this reward + var/icon_state = "coin_diamond" as text + /// What category this item falls under (armor, toy, etc) + var/category + /// If this item can bypass the 1-per-category limit + var/category_limit_bypass = FALSE + var/lifeform_type = "Marine" + +/datum/battlepass_reward/proc/can_claim(mob/target_mob) + if(!iscarbon(target_mob)) + return FALSE + + var/mob/living/carbon/carbon_mob = target_mob + + if((category in carbon_mob.claimed_reward_categories) && !category_limit_bypass) + return FALSE + + return TRUE + +/datum/battlepass_reward/proc/on_claim(mob/target_mob) + return + +/datum/battlepass_reward/test + name = "Debug" + icon = 'icons/obj/items/items.dmi' + icon_state = "coin_diamond" diff --git a/code/modules/battlepass/rewards/code/particles.dm b/code/modules/battlepass/rewards/code/particles.dm new file mode 100644 index 000000000000..a8c1411b9bec --- /dev/null +++ b/code/modules/battlepass/rewards/code/particles.dm @@ -0,0 +1,277 @@ +///objects can only have one particle on them at a time, so we use these abstract effects to hold and display the effects. You know, so multiple particle effects can exist at once. +///also because some objects do not display particles due to how their visuals are built +/obj/effect/abstract/particle_holder + name = "particle holder" + desc = "How are you reading this? Please make a bug report :)" + appearance_flags = KEEP_APART|KEEP_TOGETHER|TILE_BOUND|PIXEL_SCALE|LONG_GLIDE //movable appearance_flags plus KEEP_APART and KEEP_TOGETHER + vis_flags = VIS_INHERIT_PLANE + layer = ABOVE_XENO_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + anchored = TRUE + /// Holds info about how this particle emitter works + /// See \code\__DEFINES\particles.dm + var/particle_flags = NONE + + var/atom/parent + +/obj/effect/abstract/particle_holder/Initialize(mapload, particle_path_or_instance, particle_flags = NONE) + . = ..() + if(!loc) + stack_trace("particle holder was created with no loc!") + return INITIALIZE_HINT_QDEL + // We nullspace ourselves because some objects use their contents (e.g. storage) and some items may drop everything in their contents on deconstruct. + parent = loc + loc = null + + // Mouse opacity can get set to opaque by some objects when placed into the object's contents (storage containers). + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + src.particle_flags = particle_flags + if(ispath(particle_path_or_instance)) + particles = new particle_path_or_instance() + else + particles = particle_path_or_instance + // /atom doesn't have vis_contents, /turf and /atom/movable do + var/atom/movable/lie_about_areas = parent + lie_about_areas.vis_contents += src + RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(parent_deleted)) + + if(particle_flags & PARTICLE_ATTACH_MOB) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) + on_move(parent, null, NORTH) + +/obj/effect/abstract/particle_holder/Destroy(force) + QDEL_NULL(particles) + parent = null + return ..() + +/// Non movables don't delete contents on destroy, so we gotta do this +/obj/effect/abstract/particle_holder/proc/parent_deleted(datum/source) + SIGNAL_HANDLER + qdel(src) + +/// signal called when a parent that's been hooked into this moves +/// does a variety of checks to ensure overrides work out properly +/obj/effect/abstract/particle_holder/proc/on_move(atom/movable/attached, atom/oldloc, direction) + SIGNAL_HANDLER + + if(!(particle_flags & PARTICLE_ATTACH_MOB)) + return + + //remove old + if(ismob(oldloc)) + var/mob/particle_mob = oldloc + particle_mob.vis_contents -= src + + // If we're sitting in a mob, we want to emit from it too, for vibes and shit + if(ismob(attached.loc)) + var/mob/particle_mob = attached.loc + particle_mob.vis_contents += src + +/// Sets the particles position to the passed coordinate list (X, Y, Z) +/// See [https://www.byond.com/docs/ref/#/{notes}/particles] for position documentation +/obj/effect/abstract/particle_holder/proc/set_particle_position(list/pos) + particles.position = pos + +/particles/proc/resize_pos(mob/assigned_mob) + return + +/particles/droplets + icon = 'icons/effects/particles/generic.dmi' + icon_state = list("dot"=2,"drop"=1) + width = 32 + height = 36 + count = 5 + spawning = 0.2 + lifespan = 1 SECONDS + fade = 0.5 SECONDS + color = "#549EFF" + position = generator(GEN_BOX, list(-9,-9,0), list(9,18,0), NORMAL_RAND) + scale = generator(GEN_VECTOR, list(0.9,0.9), list(1.1,1.1), NORMAL_RAND) + gravity = list(0, -0.9) + +/particles/droplets/resize_pos(mob/assigned_mob) + var/is = assigned_mob.icon_size / 32 + position = generator(GEN_BOX, list(-9*is, -9*is, 0), list(9*is,18*is,0), NORMAL_RAND) + +/particles/slime + icon = 'icons/effects/particles/goop.dmi' + icon_state = list("goop_1" = 6, "goop_2" = 2, "goop_3" = 1) + width = 100 + height = 100 + count = 100 + spawning = 0.5 + color = "#4b4a4aa0" + lifespan = 1.5 SECONDS + fade = 1 SECONDS + grow = -0.025 + gravity = list(0, -0.05) + position = generator(GEN_BOX, list(-8,-16,0), list(8,16,0), NORMAL_RAND) + spin = generator(GEN_NUM, -15, 15, NORMAL_RAND) + scale = list(0.75, 0.75) + +/particles/slime/resize_pos(mob/assigned_mob) + var/is = assigned_mob.icon_size / 32 + position = generator(GEN_BOX, list(-8*is, -16*is, 0), list(8*is,16*is,0), NORMAL_RAND) + +/// Rainbow slime particles. +/particles/slime/rainbow + gradient = list(0, "#f00a", 3, "#0ffa", 6, "#f00a", "loop", "space"=COLORSPACE_HSL) + color_change = 0.2 + color = generator(GEN_NUM, 0, 6, UNIFORM_RAND) + +/particles/pollen + icon = 'icons/effects/particles/pollen.dmi' + icon_state = "pollen" + width = 100 + height = 100 + count = 1000 + spawning = 4 + lifespan = 0.7 SECONDS + fade = 1 SECONDS + grow = -0.01 + velocity = list(0, 0) + position = generator(GEN_CIRCLE, 0, 16, NORMAL_RAND) + drift = generator(GEN_VECTOR, list(0, -0.2), list(0, 0.2)) + gravity = list(0, 0.95) + scale = generator(GEN_VECTOR, list(0.3, 0.3), list(1,1), NORMAL_RAND) + rotation = 30 + spin = generator(GEN_NUM, -20, 20) + +/particles/pollen/resize_pos(mob/assigned_mob) + var/is = assigned_mob.icon_size / 32 + position = generator(GEN_CIRCLE, 0, 16*is, NORMAL_RAND) + + +/particles/stink + icon = 'icons/effects/particles/stink.dmi' + icon_state = list("stink_1" = 1, "stink_2" = 2, "stink_3" = 2) + color = "#0BDA51" + width = 100 + height = 100 + count = 25 + spawning = 0.25 + lifespan = 1 SECONDS + fade = 1 SECONDS + position = generator(GEN_CIRCLE, 0, 16, UNIFORM_RAND) + gravity = list(0, 0.25) + +/particles/stink/resize_pos(mob/assigned_mob) + var/is = assigned_mob.icon_size / 32 + position = generator(GEN_CIRCLE, 0, 16*is, NORMAL_RAND) + +/particles/musical_notes + icon = 'icons/effects/particles/notes/note.dmi' + icon_state = list( + "note_1" = 1, + "note_2" = 1, + "note_3" = 1, + "note_4" = 1, + "note_5" = 1, + "note_6" = 1, + "note_7" = 1, + "note_8" = 1, + ) + width = 100 + height = 100 + count = 250 + spawning = 0.3 + lifespan = 0.7 SECONDS + fade = 1 SECONDS + grow = -0.01 + velocity = list(0, 0) + position = generator(GEN_CIRCLE, 0, 16, NORMAL_RAND) + drift = generator(GEN_VECTOR, list(0, -0.2), list(0, 0.2)) + gravity = list(0, 0.95) + +/particles/musical_notes/resize_pos(mob/assigned_mob) + var/is = assigned_mob.icon_size / 32 + position = generator(GEN_CIRCLE, 0, 16*is, NORMAL_RAND) + +/particles/musical_notes/holy + icon = 'icons/effects/particles/notes/note_holy.dmi' + icon_state = list( + "holy_1" = 1, + "holy_2" = 1, + "holy_3" = 1, + "holy_4" = 1, + "holy_5" = 1, + "holy_6" = 1, + "holy_7" = 1, + "holy_8" = 1, + "holy_9" = 4, //holy theme specific + ) + +/particles/musical_notes/nullwave + icon = 'icons/effects/particles/notes/note_null.dmi' + icon_state = list( + "null_1" = 1, + "null_2" = 1, + "null_3" = 1, + "null_4" = 1, + "null_5" = 1, + "null_6" = 1, + "null_7" = 1, + "null_8" = 1, + "null_9" = 2, //heal theme specific + "null_10" = 2, //heal theme specific + ) + +/particles/musical_notes/harm + icon = 'icons/effects/particles/notes/note_harm.dmi' + icon_state = list( + "harm_1" = 1, + "harm_2" = 1, + "harm_3" = 1, + "harm_4" = 1, + "harm_5" = 1, + "harm_6" = 1, + "harm_7" = 1, + "harm_8" = 1, + "harm_9" = 2, //harm theme specific + "harm_10" = 2, //harm theme specific + ) + +/particles/musical_notes/sleepy + icon = 'icons/effects/particles/notes/note_sleepy.dmi' + icon_state = list( + "sleepy_1" = 1, + "sleepy_2" = 1, + "sleepy_3" = 1, + "sleepy_4" = 1, + "sleepy_5" = 1, + "sleepy_6" = 1, + "sleepy_7" = 1, + "sleepy_8" = 1, + "sleepy_9" = 2, //sleepy theme specific + "sleepy_10" = 2, //sleepy theme specific + ) + +/particles/musical_notes/light + icon = 'icons/effects/particles/notes/note_light.dmi' + icon_state = list( + "power_1" = 1, + "power_2" = 1, + "power_3" = 1, + "power_4" = 1, + "power_5" = 1, + "power_6" = 1, + "power_7" = 1, + "power_8" = 1, + "power_9" = 2, //light theme specific + "power_10" = 2, //light theme specific + ) + +/particles/acid + icon = 'icons/effects/particles/goop.dmi' + icon_state = list("goop_1" = 6, "goop_2" = 2, "goop_3" = 1) + width = 100 + height = 100 + count = 100 + spawning = 0.5 + color = "#00ea2b80" //to get 96 alpha + lifespan = 1.5 SECONDS + fade = 1 SECONDS + grow = -0.025 + gravity = list(0, 0.15) + position = generator(GEN_SPHERE, 0, 16, NORMAL_RAND) + spin = generator(GEN_NUM, -15, 15, NORMAL_RAND) diff --git a/code/modules/battlepass/rewards/general_rewards.dm b/code/modules/battlepass/rewards/general_rewards.dm new file mode 100644 index 000000000000..c3efb32e5eef --- /dev/null +++ b/code/modules/battlepass/rewards/general_rewards.dm @@ -0,0 +1,49 @@ +/datum/battlepass_reward/general + lifeform_type = "All" + + +/datum/battlepass_reward/general/particle + category = REWARD_CATEGORY_PARTICLE + var/particle_path + +/datum/battlepass_reward/general/particle/on_claim(mob/target_mob) + var/particles/new_particle = new particle_path() + new_particle.resize_pos(target_mob) + target_mob.particle_holder = new(target_mob, new_particle) + if(target_mob.icon_size == 48) + target_mob.particle_holder.pixel_x = 16 + else if(target_mob.icon_size == 64) + target_mob.particle_holder.pixel_x = 16 + target_mob.particle_holder.pixel_y = 16 + return TRUE + + +/datum/battlepass_reward/general/particle/droplets + name = "Water Particles" + icon_state = "droplets" + particle_path = /particles/droplets + +/datum/battlepass_reward/general/particle/acid + name = "Acid Particles" + icon_state = "acid" + particle_path = /particles/acid + +/datum/battlepass_reward/general/particle/music + name = "Musical Particles" + icon_state = "notes" + particle_path = /particles/musical_notes + +/datum/battlepass_reward/general/particle/slime + name = "Slime Particles" + icon_state = "slime" + particle_path = /particles/slime + +/datum/battlepass_reward/general/particle/slime_rainbow + name = "Rbw. Slime Particles" + icon_state = "rainbow_slime" + particle_path = /particles/slime/rainbow + +/datum/battlepass_reward/general/particle/pollen + name = "Pollen Particles" + icon_state = "pollen" + particle_path = /particles/pollen diff --git a/code/modules/battlepass/rewards/marine_rewards.dm b/code/modules/battlepass/rewards/marine_rewards.dm new file mode 100644 index 000000000000..11f44c58ae9f --- /dev/null +++ b/code/modules/battlepass/rewards/marine_rewards.dm @@ -0,0 +1,264 @@ +/datum/battlepass_reward/marine + lifeform_type = "Marine" + +/datum/battlepass_reward/marine/can_claim(mob/target_mob) + . = ..() + if(!.) + return . + + if(!ishuman(target_mob)) + return FALSE + + return TRUE + + +/datum/battlepass_reward/marine/diamond_armor + name = "Diamond Armor" + icon_state = "diamond_armor" + category = REWARD_CATEGORY_ARMOR + +/datum/battlepass_reward/marine/diamond_armor/on_claim(mob/target_mob) + if(!ishuman(target_mob)) + return FALSE + + var/mob/living/carbon/human/target_human = target_mob + if(!istype(target_human.wear_suit, /obj/item/clothing/suit/storage/marine)) + to_chat(target_human, SPAN_WARNING("You need to be wearing marine armor to claim this!")) + return FALSE + + var/obj/item/clothing/suit/storage/marine/marine_armor = target_human.wear_suit + if(marine_armor.flags_inventory & BLOCK_KNOCKDOWN) // a bit of fairness + to_chat(target_human, SPAN_WARNING("Armor that blocks knockdowns cannot have a skin!")) + return FALSE + + marine_armor.flags_marine_armor &= ~ARMOR_SQUAD_OVERLAY + marine_armor.flags_atom |= NO_SNOW_TYPE + marine_armor.armor_variation = FALSE + marine_armor.icon = 'code/modules/battlepass/rewards/sprites/armorobj.dmi' + marine_armor.icon_state = "diamond_armor" + marine_armor.name = "diamond [marine_armor.name]" + marine_armor.desc = "This suit of diamond armor looks impossible to get into. How the hell would anyone be able to afford this?" + marine_armor.item_icons = list( + WEAR_JACKET = 'code/modules/battlepass/rewards/sprites/armor.dmi' + ) + marine_armor.update_icon() + marine_armor.update_clothing_icon() + + if(target_human.shoes) + target_human.shoes.flags_atom |= NO_SNOW_TYPE + target_human.shoes.icon = 'code/modules/battlepass/rewards/sprites/armorobj.dmi' + target_human.shoes.icon_state = "diamond_boots" + target_human.shoes.item_icons = list( + WEAR_FEET = 'code/modules/battlepass/rewards/sprites/armor.dmi' + ) + if(istype(target_human.shoes, /obj/item/clothing/shoes/marine)) + var/obj/item/clothing/shoes/marine/marine_shoes = target_human.shoes + marine_shoes.base_icon_state = "diamond_boots" + + target_human.shoes.name = "diamond [target_human.shoes]" + target_human.shoes.desc = "This pair of diamond boots look impossible to walk around in, but you think you can manage, somehow." + target_human.shoes.update_icon() + target_human.update_inv_shoes() + + if(istype(target_human.head, /obj/item/clothing/head/helmet/marine)) + var/obj/item/clothing/head/helmet/marine/marine_helmet = target_human.head + target_human.head.flags_atom |= NO_SNOW_TYPE + marine_helmet.flags_marine_helmet = NONE + marine_helmet.icon = 'code/modules/battlepass/rewards/sprites/armorobj.dmi' + marine_helmet.icon_state = "diamond_helmet" + marine_helmet.name = "diamond [marine_helmet.name]" + marine_helmet.desc = "A diamond helmet. This thing is definitely better than whatever the corps would give you, that's for sure." + marine_helmet.item_icons = list( + WEAR_HEAD = 'code/modules/battlepass/rewards/sprites/armor.dmi' + ) + marine_helmet.update_icon() + return TRUE + + +/datum/battlepass_reward/marine/toy + category = REWARD_CATEGORY_TOY + var/item_path + +/datum/battlepass_reward/marine/toy/on_claim(mob/target_mob) + if(!ishuman(target_mob) || !item_path) + return FALSE + + var/mob/living/carbon/human/target_human = target_mob + var/obj/item/new_toy = new item_path + target_human.put_in_hands(new_toy, TRUE) + return TRUE + + +/datum/battlepass_reward/marine/toy/sus_crayon + name = "Suspicious Crayon" + icon_state = "sus_crayon" + item_path = /obj/item/toy/suspicious + + +/datum/battlepass_reward/marine/toy/runner_toy + name = "Runner Toy" + icon = 'code/modules/battlepass/rewards/sprites/toy.dmi' + icon_state = "runner_toy" + item_path = /obj/item/toy/plush/runner_toy + + +/datum/battlepass_reward/marine/toy/warrior_toy + name = "Warrior Toy" + icon = 'code/modules/battlepass/rewards/sprites/toy.dmi' + icon_state = "warrior_toy" + item_path = /obj/item/toy/plush/warrior_toy + + +/datum/battlepass_reward/marine/toy/queen_toy + name = "Queen Toy" + icon = 'code/modules/battlepass/rewards/sprites/toy.dmi' + icon_state = "queen_toy" + item_path = /obj/item/toy/plush/queen_toy + + +/datum/battlepass_reward/marine/toy/bikehorn + name = "Bike Horn" + icon = 'icons/obj/items/items.dmi' + icon_state = "bike_horn" + item_path = /obj/item/toy/bikehorn + + +/obj/item/clothing/head/helmet/marine + var/mutable_appearance/helmet_fire_overlay_icon + var/mutable_appearance/helmet_fire_overlay_mob_icon + +/datum/battlepass_reward/marine/helmet_fire + name = "Helmet Fire" + icon = 'code/modules/battlepass/rewards/sprites/helmetfire.dmi' + icon_state = "orange_static" + category = REWARD_CATEGORY_HELMET_FIRE + /// The iconstate to give the helmet's fire + var/helmet_fire_iconstate + +/datum/battlepass_reward/marine/helmet_fire/on_claim(mob/target_mob) + if(!ishuman(target_mob) || !helmet_fire_iconstate) + return FALSE + + var/mob/living/carbon/human/target_human = target_mob + if(!istype(target_human.head, /obj/item/clothing/head/helmet/marine)) + to_chat(target_human, SPAN_WARNING("You can't claim this unless you're wearing a marine helmet!")) + return FALSE + + var/obj/item/clothing/head/helmet/marine/helmet = target_human.head + helmet.helmet_fire_overlay_icon = mutable_appearance('code/modules/battlepass/rewards/sprites/helmetfire.dmi', helmet_fire_iconstate) + helmet.helmet_fire_overlay_mob_icon = mutable_appearance('code/modules/battlepass/rewards/sprites/helmetfire.dmi', helmet_fire_iconstate) + helmet.helmet_fire_overlay_mob_icon.pixel_x = -16 + helmet.helmet_fire_overlay_mob_icon.pixel_y = -16 + helmet.helmet_fire_overlay_icon.pixel_x = -16 + helmet.helmet_fire_overlay_icon.pixel_y = -24 + helmet.update_icon() + return TRUE + +/datum/battlepass_reward/marine/helmet_fire/orange + name = "Helmet Fire (Orange)" + icon_state = "orange_static" + helmet_fire_iconstate = "orange" + +/datum/battlepass_reward/marine/helmet_fire/green + name = "Helmet Fire (Green)" + icon_state = "green_static" + helmet_fire_iconstate = "green" + +/datum/battlepass_reward/marine/helmet_fire/blue + name = "Helmet Fire (Blue)" + icon_state = "blue_static" + helmet_fire_iconstate = "blue" + +/datum/battlepass_reward/marine/helmet_fire/purple + name = "Helmet Fire (Purple)" + icon_state = "purple_static" + helmet_fire_iconstate = "purple" + + +/datum/battlepass_reward/marine/m41a_reskin + name = "Golden M41A MK2" + icon_state = "m41a" + category = REWARD_CATEGORY_M41A_RESKIN + +/datum/battlepass_reward/marine/m41a_reskin/on_claim(mob/target_mob) + if(!ishuman(target_mob)) + return FALSE + + var/list/valid_rifles = list( + /obj/item/weapon/gun/rifle/m41a, + /obj/item/weapon/gun/rifle/m41a/stripped, + /obj/item/weapon/gun/rifle/m41a/tactical, + /obj/item/weapon/gun/rifle/m41a/training, + ) + + var/mob/living/carbon/human/target_human = target_mob + + var/obj/item/weapon/gun/rifle/m41a/gotten_rifle + if(target_human.l_hand && (target_human.l_hand.type in valid_rifles)) + gotten_rifle = target_human.l_hand + + else if(target_human.r_hand && (target_human.r_hand.type in valid_rifles)) + gotten_rifle = target_human.r_hand + + if(!gotten_rifle) + to_chat(target_mob, SPAN_WARNING("You need an M41A MK2 rifle in a hand to claim this.")) + return + + gotten_rifle.base_gun_icon = "[gotten_rifle.base_gun_icon]_golden" + gotten_rifle.item_state = "m41a_golden" + gotten_rifle.map_specific_decoration = FALSE + gotten_rifle.icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi' + gotten_rifle.item_icons = list( + WEAR_L_HAND = 'icons/mob/humans/onmob/items_lefthand_1.dmi', + WEAR_R_HAND = 'icons/mob/humans/onmob/items_righthand_1.dmi', + WEAR_J_STORE = 'icons/mob/humans/onmob/suit_slot.dmi', + WEAR_BACK = 'icons/mob/humans/onmob/back.dmi', + ) + gotten_rifle.item_state_slots = list( + WEAR_BACK = 'icons/mob/humans/onmob/back.dmi', + ) + gotten_rifle.update_icon() + target_human.update_inv_l_hand() + target_human.update_inv_r_hand() + return TRUE + + +/datum/battlepass_reward/marine/m37_reskin + name = "Golden M37A2" + icon_state = "m37" + category = REWARD_CATEGORY_SHOTGUN_RESKIN + +/datum/battlepass_reward/marine/m37_reskin/on_claim(mob/target_mob) + if(!ishuman(target_mob)) + return FALSE + + var/mob/living/carbon/human/target_human = target_mob + + var/obj/item/weapon/gun/shotgun/pump/gotten_rifle + if(target_human.l_hand && (target_human.l_hand.type == /obj/item/weapon/gun/shotgun/pump)) + gotten_rifle = target_human.l_hand + + else if(target_human.r_hand && (target_human.r_hand.type == /obj/item/weapon/gun/shotgun/pump)) + gotten_rifle = target_human.r_hand + + if(!gotten_rifle) + to_chat(target_mob, SPAN_WARNING("You need an M37A2 shotgun in a hand to claim this.")) + return + + gotten_rifle.base_gun_icon = "[gotten_rifle.base_gun_icon]_golden" + gotten_rifle.item_state = "m37_golden" + gotten_rifle.map_specific_decoration = FALSE + gotten_rifle.icon = 'icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi' + gotten_rifle.item_icons = list( + WEAR_L_HAND = 'icons/mob/humans/onmob/items_lefthand_1.dmi', + WEAR_R_HAND = 'icons/mob/humans/onmob/items_righthand_1.dmi', + WEAR_J_STORE = 'icons/mob/humans/onmob/suit_slot.dmi', + WEAR_BACK = 'icons/mob/humans/onmob/back.dmi', + ) + gotten_rifle.item_state_slots = list( + WEAR_BACK = 'icons/mob/humans/onmob/back.dmi', + ) + gotten_rifle.update_icon() + target_human.update_inv_l_hand() + target_human.update_inv_r_hand() + return TRUE diff --git a/code/modules/battlepass/rewards/premium_rewards.dm b/code/modules/battlepass/rewards/premium_rewards.dm new file mode 100644 index 000000000000..a2883e99b23e --- /dev/null +++ b/code/modules/battlepass/rewards/premium_rewards.dm @@ -0,0 +1,69 @@ +/datum/battlepass_reward/spec_token + name = "Specialist Token" + icon = 'icons/obj/items/items.dmi' + icon_state = "coin_diamond" + +/datum/battlepass_reward/heap_mag + name = "HEAP M41A Mag" + icon_state = "heap_mag" + +/datum/battlepass_reward/cat_ears + name = "Cat Ears" + icon_state = "cat_ears" + +/datum/battlepass_reward/damage_boost + name = "Damage Increase" + icon_state = "damage_boost" + lifeform_type = "All" + +/datum/battlepass_reward/extra_health + name = "25% Health Increase" + icon_state = "extra_health" + lifeform_type = "All" + +/datum/battlepass_reward/stims + name = "NST5MST5 Stim" + icon_state = "stims" + +/datum/battlepass_reward/hoverpack + name = "Hoverpack" + icon = 'icons/obj/items/devices.dmi' + icon_state = "hoverpack" + +/datum/battlepass_reward/predalien_larva + name = "Predalien Larva" + icon_state = "predalien_larva" + lifeform_type = "Xeno" + +/datum/battlepass_reward/hugger_skin + name = "Facehugger Skin" + icon_state = "cs_source" + lifeform_type = "Xeno" + +/datum/battlepass_reward/navy_l42a + name = "Navy L42A" + icon_state = "navy_l42a" + +/datum/battlepass_reward/chrome_xm88 + name = "Chrome XM88" + icon_state = "chrome_xm88" + +/datum/battlepass_reward/custom_m39 + name = "Custom M39" + icon_state = "custom_m39" + +/datum/battlepass_reward/arctic_m41a + name = "Arctic M41A" + icon_state = "arctic_m41a" + +/datum/battlepass_reward/furnished_m37 + name = "Mangrove M37A2" + icon_state = "furnished_shotgun" + +/datum/battlepass_reward/bow + name = "Low-Res Bow" + icon_state = "bow" + +/datum/battlepass_reward/swamp_m41a + name = "Swamp M41A" + icon_state = "swamp_m41a" diff --git a/code/modules/battlepass/rewards/sprites/armor.dmi b/code/modules/battlepass/rewards/sprites/armor.dmi new file mode 100644 index 000000000000..983494feffd8 Binary files /dev/null and b/code/modules/battlepass/rewards/sprites/armor.dmi differ diff --git a/code/modules/battlepass/rewards/sprites/armorobj.dmi b/code/modules/battlepass/rewards/sprites/armorobj.dmi new file mode 100644 index 000000000000..cf1ca3985e83 Binary files /dev/null and b/code/modules/battlepass/rewards/sprites/armorobj.dmi differ diff --git a/code/modules/battlepass/rewards/sprites/battlepass.dmi b/code/modules/battlepass/rewards/sprites/battlepass.dmi new file mode 100644 index 000000000000..1d5da686f4df Binary files /dev/null and b/code/modules/battlepass/rewards/sprites/battlepass.dmi differ diff --git a/code/modules/battlepass/rewards/sprites/halo.dmi b/code/modules/battlepass/rewards/sprites/halo.dmi new file mode 100644 index 000000000000..a7420d2b6073 Binary files /dev/null and b/code/modules/battlepass/rewards/sprites/halo.dmi differ diff --git a/code/modules/battlepass/rewards/sprites/halo_red.dmi b/code/modules/battlepass/rewards/sprites/halo_red.dmi new file mode 100644 index 000000000000..f070a7798b2c Binary files /dev/null and b/code/modules/battlepass/rewards/sprites/halo_red.dmi differ diff --git a/code/modules/battlepass/rewards/sprites/helmetfire.dmi b/code/modules/battlepass/rewards/sprites/helmetfire.dmi new file mode 100644 index 000000000000..692fc685b937 Binary files /dev/null and b/code/modules/battlepass/rewards/sprites/helmetfire.dmi differ diff --git a/code/modules/battlepass/rewards/sprites/toy.dmi b/code/modules/battlepass/rewards/sprites/toy.dmi new file mode 100644 index 000000000000..b84c6b13ccd7 Binary files /dev/null and b/code/modules/battlepass/rewards/sprites/toy.dmi differ diff --git a/code/modules/battlepass/rewards/xeno_rewards.dm b/code/modules/battlepass/rewards/xeno_rewards.dm new file mode 100644 index 000000000000..bd09d556da9d --- /dev/null +++ b/code/modules/battlepass/rewards/xeno_rewards.dm @@ -0,0 +1,50 @@ +/datum/battlepass_reward/xeno + lifeform_type = "Xeno" + +/datum/battlepass_reward/xeno/can_claim(mob/target_mob) + . = ..() + if(!.) + return . + + if(!isxeno(target_mob)) + return FALSE + + return TRUE + +/mob/living/carbon/xenomorph + var/has_halo = FALSE as num + +/mob/living/carbon/xenomorph/proc/get_halo_iconname() + return lowertext(caste_type) + +/mob/living/carbon/xenomorph/praetorian/get_halo_iconname() + if(!strain) + return "basePrae" + + switch(strain.type) + if(/datum/xeno_strain/vanguard) + return "vanguardPrae" + if(/datum/xeno_strain/dancer) + return "dancerPrae" + if(/datum/xeno_strain/warden) + return "wardenPrae" + if(/datum/xeno_strain/oppressor) + return "oppPrae" + +/datum/battlepass_reward/xeno/halo + name = "Golden Halo" + icon_state = "golden_halo" + category = REWARD_CATEGORY_OVERLAY + +/datum/battlepass_reward/xeno/halo/on_claim(mob/living/carbon/xenomorph/target_mob) + target_mob.create_halo() + return TRUE + +/datum/battlepass_reward/xeno/evil_halo + name = "Red Halo" + icon_state = "red_halo" + category = REWARD_CATEGORY_OVERLAY + +/datum/battlepass_reward/xeno/evil_halo/on_claim(mob/living/carbon/xenomorph/target_mob) + target_mob.create_evil_halo() + return TRUE diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index 26c92f632ee2..b78e69ce1be9 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -557,6 +557,7 @@ GLOBAL_LIST_INIT(allowed_helmet_items, list( // the human sprite is the only thing that reliably renders things, so // we have to add overlays to that. helmet_overlays = list() // Rebuild our list every time + overlays.Cut() if(pockets && pockets.contents.len && (flags_marine_helmet & HELMET_GARB_OVERLAY)) var/list/above_band_layer = list() var/list/below_band_layer = list() @@ -584,6 +585,9 @@ GLOBAL_LIST_INIT(allowed_helmet_items, list( if(active_visor) helmet_overlays += active_visor.helmet_overlay + if(helmet_fire_overlay_icon) + overlays += helmet_fire_overlay_icon + if(ismob(loc)) var/mob/M = loc M.update_inv_head() diff --git a/code/modules/clothing/shoes/marine_shoes.dm b/code/modules/clothing/shoes/marine_shoes.dm index 7855075c2fb4..9fb501f844c9 100644 --- a/code/modules/clothing/shoes/marine_shoes.dm +++ b/code/modules/clothing/shoes/marine_shoes.dm @@ -28,12 +28,17 @@ /obj/item/weapon/straight_razor, ) drop_sound = "armorequip" + var/base_icon_state + +/obj/item/clothing/shoes/marine/Initialize(mapload, ...) + base_icon_state = initial(icon_state) + . = ..() /obj/item/clothing/shoes/marine/update_icon() if(stored_item) - icon_state = "[initial(icon_state)]-1" + icon_state = "[base_icon_state]-1" else - icon_state = initial(icon_state) + icon_state = base_icon_state /obj/item/clothing/shoes/marine/knife spawn_item_type = /obj/item/attachable/bayonet diff --git a/code/modules/cm_aliens/structures/special/pylon_core.dm b/code/modules/cm_aliens/structures/special/pylon_core.dm index add9646c56ac..1cafab0655dc 100644 --- a/code/modules/cm_aliens/structures/special/pylon_core.dm +++ b/code/modules/cm_aliens/structures/special/pylon_core.dm @@ -309,6 +309,7 @@ if(new_xeno.client) if(new_xeno.client.prefs.toggles_flashing & FLASH_POOLSPAWN) window_flash(new_xeno.client) + SSbattlepass.xeno_battlepass_earners |= new_xeno.client.ckey linked_hive.stored_larva-- linked_hive.hive_ui.update_burrowed_larva() diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 9a0cd177e885..b2bc6e87b6bf 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -447,6 +447,8 @@ Applied by gun suicide and high impact bullet executions, removed by rejuvenate, for(var/i in HEAD_GARB_LAYER to (HEAD_GARB_LAYER + MAX_HEAD_GARB_ITEMS - 1)) remove_overlay(i) + remove_overlay(MAX_HEAD_GARB_ITEMS + 1) + if(head) if(client && hud_used && hud_used.hud_shown && hud_used.inventory_shown && hud_used.ui_datum) @@ -484,9 +486,14 @@ Applied by gun suicide and high impact bullet executions, removed by rejuvenate, for(var/i in num_helmet_overlays+1 to MAX_HEAD_GARB_ITEMS) overlays_standing[HEAD_GARB_LAYER + (i-1)] = null + if(marine_helmet.helmet_fire_overlay_mob_icon) + overlays_standing[MAX_HEAD_GARB_ITEMS + 1] = marine_helmet.helmet_fire_overlay_mob_icon + for(var/i in HEAD_GARB_LAYER to (HEAD_GARB_LAYER + MAX_HEAD_GARB_ITEMS - 1)) apply_overlay(i) + apply_overlay(MAX_HEAD_GARB_ITEMS + 1) + #undef MAX_HEAD_GARB_ITEMS diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_powers.dm index 054762b3c7d4..d14ac359913e 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/facehugger/facehugger_powers.dm @@ -16,9 +16,12 @@ var/key_name = key_name(facehugger) var/did_hug = FALSE + var/client/hugging_client = facehugger.client if(facehugger.pounce_distance <= 1 && can_hug(L, facehugger.hivenumber)) did_hug = facehugger.handle_hug(L) log_attack("[key_name] [did_hug ? "successfully hugged" : "tried to hug"] [key_name(L)] (Pounce Distance: [facehugger.pounce_distance]) at [get_location_in_text(L)]") + if(did_hug && hugging_client) + SEND_SIGNAL(hugging_client.mob, COMSIG_XENO_FACEHUGGED_HUMAN) //handle_hug deletes the hugger /datum/action/xeno_action/activable/pounce/facehugger/use_ability() for(var/obj/structure/machinery/door/airlock/current_airlock in get_turf(owner)) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm index 05ab5d00a743..9dae27709dec 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm @@ -78,7 +78,7 @@ playsound(xeno.loc, "alien_resin_build", 25) apply_cooldown() - SEND_SIGNAL(xeno, COMSIG_XENO_PLANT_RESIN_NODE) + SEND_SIGNAL(xeno, COMSIG_XENO_PLANT_RESIN_NODE, xeno) return ..() /mob/living/carbon/xenomorph/lay_down() diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status.dm b/code/modules/mob/living/carbon/xenomorph/hive_status.dm index 22b061715892..3630f178a35b 100644 --- a/code/modules/mob/living/carbon/xenomorph/hive_status.dm +++ b/code/modules/mob/living/carbon/xenomorph/hive_status.dm @@ -791,6 +791,7 @@ if(new_xeno.client) if(new_xeno.client?.prefs?.toggles_flashing & FLASH_POOLSPAWN) window_flash(new_xeno.client) + SSbattlepass.xeno_battlepass_earners |= new_xeno stored_larva-- hive_ui.update_burrowed_larva() diff --git a/code/modules/mob/living/carbon/xenomorph/login.dm b/code/modules/mob/living/carbon/xenomorph/login.dm index e381aea4015e..8610f31ec091 100644 --- a/code/modules/mob/living/carbon/xenomorph/login.dm +++ b/code/modules/mob/living/carbon/xenomorph/login.dm @@ -4,5 +4,6 @@ set_lighting_alpha_from_prefs(client) if(client.player_data) generate_name() + SSbattlepass.xeno_battlepass_earners |= client.ckey if(SSticker.mode) SSticker.mode.xenomorphs |= mind diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/gardener.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/gardener.dm index d54d268f12d9..803db9424440 100644 --- a/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/gardener.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/drone/gardener.dm @@ -104,6 +104,7 @@ SPAN_XENONOTICE("We secrete a portion of our vital fluids and shape them into a fruit!"), null, 5) var/obj/effect/alien/resin/fruit/fruit = new xeno.selected_fruit(target_weeds.loc, target_weeds, xeno) + SEND_SIGNAL(xeno, COMSIG_XENO_PLANTED_FRUIT, xeno) if(!fruit) to_chat(xeno, SPAN_XENOHIGHDANGER("Couldn't find the fruit to place! Contact a coder!")) return diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm index c12324aa5b2a..6a528c158d0c 100644 --- a/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/ravager/berserker.dm @@ -71,6 +71,7 @@ bound_xeno.add_filter("berserker_rage", 1, list("type" = "outline", "color" = "#000000ff", "size" = 1)) rage_lock() to_chat(bound_xeno, SPAN_XENOHIGHDANGER("We feel a euphoric rush as we reach max rage! We are LOCKED at max Rage!")) + SEND_SIGNAL(bound_xeno, COMSIG_XENO_RAGE_MAX) // HP vamp bound_xeno.gain_health((0.05*rage + hp_vamp_ratio)*((bound_xeno.melee_damage_upper - bound_xeno.melee_damage_lower)/2 + bound_xeno.melee_damage_lower)) diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/runner/acid.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/runner/acid.dm index 7b9bafadeb7b..a43c018c0841 100644 --- a/code/modules/mob/living/carbon/xenomorph/strains/castes/runner/acid.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/runner/acid.dm @@ -119,6 +119,8 @@ var/acid_range = acid_amount / caboom_acid_ratio var/max_burn_damage = acid_amount / caboom_burn_damage_ratio var/burn_range = acid_amount / caboom_burn_range_ratio + if(acid_amount >= max_acid) + SEND_SIGNAL(bound_xeno, COMSIG_XENO_FTH_MAX_ACID) for(var/barricades in view(bound_xeno, acid_range)) if(istype(barricades, /obj/structure/barricade)) diff --git a/code/modules/mob/living/carbon/xenomorph/update_icons.dm b/code/modules/mob/living/carbon/xenomorph/update_icons.dm index 635bdf856208..bc9fb3b7a74e 100644 --- a/code/modules/mob/living/carbon/xenomorph/update_icons.dm +++ b/code/modules/mob/living/carbon/xenomorph/update_icons.dm @@ -2,6 +2,7 @@ //Abby //Xeno Overlays Indexes////////// +#define X_HALO_LAYER 11 #define X_BACK_LAYER 10 #define X_HEAD_LAYER 9 #define X_SUIT_LAYER 8 @@ -12,7 +13,7 @@ #define X_TARGETED_LAYER 3 #define X_LEGCUFF_LAYER 2 #define X_FIRE_LAYER 1 -#define X_TOTAL_LAYERS 10 +#define X_TOTAL_LAYERS 11 ///////////////////////////////// @@ -250,6 +251,22 @@ apply_overlay(X_SUIT_LAYER) addtimer(CALLBACK(src, PROC_REF(remove_overlay), X_SUIT_LAYER), 20) +/mob/living/carbon/xenomorph/proc/create_halo() + if(has_halo) + return + + overlays_standing[X_HALO_LAYER] = image("icon" = 'code/modules/battlepass/rewards/sprites/halo.dmi', "icon_state" = get_halo_iconname()) + apply_overlay(X_HALO_LAYER) + has_halo = TRUE + +/mob/living/carbon/xenomorph/proc/create_evil_halo() + if(has_halo) + return + + overlays_standing[X_HALO_LAYER] = image("icon" = 'code/modules/battlepass/rewards/sprites/halo_red.dmi', "icon_state" = get_halo_iconname()) + apply_overlay(X_HALO_LAYER) + has_halo = TRUE + /mob/living/carbon/xenomorph/proc/create_custom_empower(icolor, ialpha = 255, small_xeno = FALSE) remove_suit_layer() @@ -351,3 +368,4 @@ #undef X_R_HAND_LAYER #undef X_LEGCUFF_LAYER #undef X_FIRE_LAYER +#undef X_HALO_LAYER diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index 7917394df830..fb6d9bc31377 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -43,6 +43,7 @@ output +="
[(client.prefs && client.prefs.real_name) ? client.prefs.real_name : client.key]" output +="
[xeno_text]" output += "

Tutorial

" + output += "

Battlepass

" output += "

Setup Character

" output += "

View Playtimes

" @@ -213,6 +214,15 @@ if("tutorial") tutorial_menu() + if("battlepass") + if(!client?.owned_battlepass) + return + + if(!SSbattlepass.initialized) + to_chat(src, SPAN_WARNING("Please wait for battlepasses to initialize first.")) + return + + client.owned_battlepass.ui_interact(src) else new_player_panel() @@ -289,6 +299,7 @@ msg_sea("NEW PLAYER: [key_name(character, 0, 1, 0)] only has [(round(client.get_total_human_playtime() DECISECONDS_TO_HOURS, 0.1))] hours as a human. Current role: [get_actual_job_name(character)] - Current location: [get_area(character)]") character.client.init_verbs() + SSbattlepass.marine_battlepass_earners |= character.client.ckey qdel(src) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 3f7533f26620..419ea26adbf5 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -1409,7 +1409,7 @@ and you're good to go. apply_bullet_effects(projectile_to_fire, user, bullets_fired, dual_wield) //We add any damage effects that we need. SEND_SIGNAL(projectile_to_fire, COMSIG_BULLET_USER_EFFECTS, user) - SEND_SIGNAL(user, COMSIG_BULLET_DIRECT_HIT, attacked_mob) + SEND_SIGNAL(user, COMSIG_BULLET_DIRECT_HIT, attacked_mob, src) simulate_recoil(1, user) if(projectile_to_fire.ammo.bonus_projectiles_amount) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index eb9b2686c3d6..bd229f11901f 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -516,7 +516,7 @@ else direct_hit = TRUE - SEND_SIGNAL(firer, COMSIG_BULLET_DIRECT_HIT, L) + SEND_SIGNAL(firer, COMSIG_BULLET_DIRECT_HIT, L, src) // At present, Xenos have no inherent effects or localized damage stemming from limb targeting // Therefore we exempt the shooter from direct hit accuracy penalties as well, @@ -1029,6 +1029,7 @@ emote("scream") to_chat(src, SPAN_HIGHDANGER("You scream in pain as the impact sends shrapnel into the wound!")) SEND_SIGNAL(P, COMSIG_POST_BULLET_ACT_HUMAN, src, damage, damage_result) + SEND_SIGNAL(P.firer, COMSIG_FIRER_PROJECTILE_DIRECT_HIT, P) //Deal with xeno bullets. /mob/living/carbon/xenomorph/bullet_act(obj/projectile/P) diff --git a/colonialmarines.dme b/colonialmarines.dme index 8cefc82f5f62..85e36a55c7c7 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -37,6 +37,7 @@ #include "code\__DEFINES\atmospherics.dm" #include "code\__DEFINES\autofire.dm" #include "code\__DEFINES\autolathe.dm" +#include "code\__DEFINES\battlepass.dm" #include "code\__DEFINES\blood.dm" #include "code\__DEFINES\bsql.config.dm" #include "code\__DEFINES\bullet_traits.dm" @@ -87,6 +88,7 @@ #include "code\__DEFINES\objects.dm" #include "code\__DEFINES\origins.dm" #include "code\__DEFINES\pain.dm" +#include "code\__DEFINES\particle.dm" #include "code\__DEFINES\pred.dm" #include "code\__DEFINES\procpath.dm" #include "code\__DEFINES\qdel.dm" @@ -256,6 +258,7 @@ #include "code\controllers\subsystem\assets.dm" #include "code\controllers\subsystem\atoms.dm" #include "code\controllers\subsystem\autofire.dm" +#include "code\controllers\subsystem\battlepass.dm" #include "code\controllers\subsystem\cellauto.dm" #include "code\controllers\subsystem\chat.dm" #include "code\controllers\subsystem\communications.dm" @@ -1509,6 +1512,33 @@ #include "code\modules\autowiki\autowiki.dm" #include "code\modules\autowiki\pages\_page.dm" #include "code\modules\autowiki\pages\guns.dm" +#include "code\modules\battlepass\battlepass.dm" +#include "code\modules\battlepass\battlepass_client.dm" +#include "code\modules\battlepass\challenges\_battlepass_challenge.dm" +#include "code\modules\battlepass\challenges\human_misc\defib_players.dm" +#include "code\modules\battlepass\challenges\kill\_kill_enemies.dm" +#include "code\modules\battlepass\challenges\kill\kill_humans.dm" +#include "code\modules\battlepass\challenges\kill\kill_xenomorphs.dm" +#include "code\modules\battlepass\challenges\kill\specific\kill_xeno_caste\_kill_caste.dm" +#include "code\modules\battlepass\challenges\kill\specific\kill_xeno_caste\kill_t1.dm" +#include "code\modules\battlepass\challenges\kill\specific\kill_xeno_caste\kill_t2.dm" +#include "code\modules\battlepass\challenges\kill\specific\kill_xeno_caste\kill_t3.dm" +#include "code\modules\battlepass\challenges\kill\specific\weapons\_kill_weapon.dm" +#include "code\modules\battlepass\challenges\kill\specific\weapons\common_weapons.dm" +#include "code\modules\battlepass\challenges\kill\specific\weapons\pistol_weapons.dm" +#include "code\modules\battlepass\challenges\kill\specific\weapons\req_weapons.dm" +#include "code\modules\battlepass\challenges\xeno_misc\berserker_empower.dm" +#include "code\modules\battlepass\challenges\xeno_misc\facehug.dm" +#include "code\modules\battlepass\challenges\xeno_misc\for_the_hive.dm" +#include "code\modules\battlepass\challenges\xeno_misc\glob_direct_hit.dm" +#include "code\modules\battlepass\challenges\xeno_misc\plant_froot.dm" +#include "code\modules\battlepass\challenges\xeno_misc\plant_resin_nodes.dm" +#include "code\modules\battlepass\rewards\_battlepass_reward.dm" +#include "code\modules\battlepass\rewards\general_rewards.dm" +#include "code\modules\battlepass\rewards\marine_rewards.dm" +#include "code\modules\battlepass\rewards\premium_rewards.dm" +#include "code\modules\battlepass\rewards\xeno_rewards.dm" +#include "code\modules\battlepass\rewards\code\particles.dm" #include "code\modules\buildmode\bm-mode.dm" #include "code\modules\buildmode\buildmode.dm" #include "code\modules\buildmode\buttons.dm" diff --git a/icons/effects/particles/bonfire.dmi b/icons/effects/particles/bonfire.dmi new file mode 100644 index 000000000000..e8e2e36346da Binary files /dev/null and b/icons/effects/particles/bonfire.dmi differ diff --git a/icons/effects/particles/echo.dmi b/icons/effects/particles/echo.dmi new file mode 100644 index 000000000000..60a243a8a7be Binary files /dev/null and b/icons/effects/particles/echo.dmi differ diff --git a/icons/effects/particles/generic.dmi b/icons/effects/particles/generic.dmi new file mode 100644 index 000000000000..41776efdbfd5 Binary files /dev/null and b/icons/effects/particles/generic.dmi differ diff --git a/icons/effects/particles/goop.dmi b/icons/effects/particles/goop.dmi new file mode 100644 index 000000000000..673c1a7ad5b6 Binary files /dev/null and b/icons/effects/particles/goop.dmi differ diff --git a/icons/effects/particles/notes/note.dmi b/icons/effects/particles/notes/note.dmi new file mode 100644 index 000000000000..766a29e6a899 Binary files /dev/null and b/icons/effects/particles/notes/note.dmi differ diff --git a/icons/effects/particles/notes/note_harm.dmi b/icons/effects/particles/notes/note_harm.dmi new file mode 100644 index 000000000000..0fe386e1ed38 Binary files /dev/null and b/icons/effects/particles/notes/note_harm.dmi differ diff --git a/icons/effects/particles/notes/note_holy.dmi b/icons/effects/particles/notes/note_holy.dmi new file mode 100644 index 000000000000..f817e8253d39 Binary files /dev/null and b/icons/effects/particles/notes/note_holy.dmi differ diff --git a/icons/effects/particles/notes/note_light.dmi b/icons/effects/particles/notes/note_light.dmi new file mode 100644 index 000000000000..3474edebc233 Binary files /dev/null and b/icons/effects/particles/notes/note_light.dmi differ diff --git a/icons/effects/particles/notes/note_null.dmi b/icons/effects/particles/notes/note_null.dmi new file mode 100644 index 000000000000..14c941f9e4ce Binary files /dev/null and b/icons/effects/particles/notes/note_null.dmi differ diff --git a/icons/effects/particles/notes/note_sleepy.dmi b/icons/effects/particles/notes/note_sleepy.dmi new file mode 100644 index 000000000000..21030914a9c0 Binary files /dev/null and b/icons/effects/particles/notes/note_sleepy.dmi differ diff --git a/icons/effects/particles/pollen.dmi b/icons/effects/particles/pollen.dmi new file mode 100644 index 000000000000..559c4d1846f6 Binary files /dev/null and b/icons/effects/particles/pollen.dmi differ diff --git a/icons/effects/particles/smoke.dmi b/icons/effects/particles/smoke.dmi new file mode 100644 index 000000000000..4a3239499b96 Binary files /dev/null and b/icons/effects/particles/smoke.dmi differ diff --git a/icons/effects/particles/stink.dmi b/icons/effects/particles/stink.dmi new file mode 100644 index 000000000000..29b92acbe67c Binary files /dev/null and b/icons/effects/particles/stink.dmi differ diff --git a/icons/mob/hud/screen1_full.dmi b/icons/mob/hud/screen1_full.dmi index c61aeaad55ee..b2386cfed965 100644 Binary files a/icons/mob/hud/screen1_full.dmi and b/icons/mob/hud/screen1_full.dmi differ diff --git a/icons/mob/humans/onmob/back.dmi b/icons/mob/humans/onmob/back.dmi index eba8a2d0289c..9dde10af0ef8 100644 Binary files a/icons/mob/humans/onmob/back.dmi and b/icons/mob/humans/onmob/back.dmi differ diff --git a/icons/mob/humans/onmob/items_lefthand_1.dmi b/icons/mob/humans/onmob/items_lefthand_1.dmi index 91dc908a8293..4d3df87424cc 100644 Binary files a/icons/mob/humans/onmob/items_lefthand_1.dmi and b/icons/mob/humans/onmob/items_lefthand_1.dmi differ diff --git a/icons/mob/humans/onmob/items_righthand_1.dmi b/icons/mob/humans/onmob/items_righthand_1.dmi index c5b67e97c2e1..9f375115e051 100644 Binary files a/icons/mob/humans/onmob/items_righthand_1.dmi and b/icons/mob/humans/onmob/items_righthand_1.dmi differ diff --git a/icons/mob/humans/onmob/suit_slot.dmi b/icons/mob/humans/onmob/suit_slot.dmi index a9142060ab75..b345063a07e4 100644 Binary files a/icons/mob/humans/onmob/suit_slot.dmi and b/icons/mob/humans/onmob/suit_slot.dmi differ diff --git a/icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi b/icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi index 1f0b98967a25..1a513d37533a 100644 Binary files a/icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi and b/icons/obj/items/weapons/guns/guns_by_faction/uscm.dmi differ diff --git a/sound/effects/bp_levelup.mp3 b/sound/effects/bp_levelup.mp3 new file mode 100644 index 000000000000..58b97c51b007 Binary files /dev/null and b/sound/effects/bp_levelup.mp3 differ diff --git a/tgui/packages/tgui/interfaces/Battlepass.tsx b/tgui/packages/tgui/interfaces/Battlepass.tsx new file mode 100644 index 000000000000..62b9d15f39b3 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Battlepass.tsx @@ -0,0 +1,342 @@ +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, LabeledList, NoticeBox, ProgressBar, Section, Dimmer, Stack } from '../components'; +import { Window } from '../layouts'; +import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; +import { classes, BooleanLike } from 'common/react'; + +interface BattlepassReward { + name: string; + icon_state: string; + tier: number; + lifeform_type: string; +} + +interface BattlepassChallenge { + name: string; + desc: string; + completed: BooleanLike; + category: string; + completion_xp: number; + completion_percent: number; + completion_numerator: number; + completion_denominator: number; +} + +interface BattlepassData { + xp: number; + tier: number; + max_tier: number; + xp_tierup: number; + rewards: BattlepassReward[]; + premium_rewards: BattlepassReward[]; + daily_challenges: BattlepassChallenge[]; +} + +export const Battlepass = (props) => { + return ( + + + + + + ); +}; + +const BattlepassContent = (props) => { + const { act, data } = useBackend(); + const rewards = data.rewards; + const premium_rewards = data.premium_rewards; + const [infoView, setInfoView] = useLocalState('info_view', false); + return ( + <> + {infoView === true ? ( + +
+
+ The battlepass system is a way of rewarding players with in-game + rewards for playing well.
+
+ On the left of the UI, you can find your objectives. These + objectives are unique to you and reset every 24 hours. Completing + them gives you XP. The other way to obtain XP is by playing a + match to completion. Everyone gets XP regardless of winning or + losing, but the winning side earns more. Whichever side you join + first will be the side you gain XP for, even if you log out before + the round ends. +

+ Every 10 XP, your battlepass tier increases by 1, granting you new + rewards to use in game. You can claim rewards with the "Claim + Battlepass Reward" verb, and come back to this UI with the + "Battlepass" verb.

+ The premium battlepass is coming soon, purchasable for an + also-coming-soon 1000 ColonialCoins. +

+
+
+
+ ) : ( + <> + )} +
+ +
+
+ Premium Battlepass coming soon! +
+
+ Premium Battlepass coming soon! +
+ {rewards.map((reward, rewardIndex) => ( + + ))} +
+
+ + ); +}; + +const BattlepassInfoContainer = (props) => { + const { act, data } = useBackend(); + const [infoView, setInfoView] = useLocalState('info_view', false); + return ( +
+
+
+
+ ); +}; + +const BattlepassChallenge = (props) => { + const challenge: BattlepassChallenge = props.challenge; + return ( +
+ {challenge.desc} +
+ + Completion: {challenge.completion_numerator} /{' '} + {challenge.completion_denominator} + + Reward: {challenge.completion_xp} XP +
+ ); +}; + +const BattlepassRegularEntry = (props) => { + const { act, data } = useBackend(); + const reward: BattlepassReward = props.reward; + const premiumReward: BattlepassReward = props.premiumReward; + return ( + <> +
+ {data.tier >= reward.tier ? ( +
+ {reward.name} +
+ ) : ( +
+ {reward.name} +
+ )} +
+
+ +
+
+ ({reward.lifeform_type}) +
+
+ {data.tier >= reward.tier ? ( +
+ {reward.tier} +
+ ) : ( +
+ {reward.tier} +
+ )} +
+
+
+ +
+
+ ({premiumReward.lifeform_type}) +
+
+
+ {premiumReward.name} +
+
+
+ + ); +};