diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm index e74803cccda9..701ce925344c 100644 --- a/code/__DEFINES/sound.dm +++ b/code/__DEFINES/sound.dm @@ -17,6 +17,7 @@ #define CHANNEL_MOB_SOUNDS 1009 #define CHANNEL_Z 1008 #define CHANNEL_WALKMAN 1007 //monkestation edit +#define CHANNEL_MASTER_VOLUME 1006 ///Default range of a sound. #define SOUND_RANGE 17 diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 07230479d473..32a0c53c44d5 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -30,6 +30,8 @@ #define IGNORE_STASIS (1<<1) /// If the incapacitated status effect will ignore a mob being agressively grabbed #define IGNORE_GRAB (1<<2) +/// If the incapacited status effect will ignore a mob in cirt +#define IGNORE_CRIT (1<<3) // Grouped effect sources, see also code/__DEFINES/traits.dm diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 31168fb1657e..e80cb4bf3e93 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -18,15 +18,32 @@ // DOES NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK /mob/proc/changeNext_move(num) - next_move = world.time + ((num+next_move_adjust)*next_move_modifier) + var/stat_multi = 1 + switch(stat) + if(SOFT_CRIT) + stat_multi = 8 + if(HARD_CRIT) + stat_multi = 16 + else + stat_multi = 1 + next_move = world.time + ((num+next_move_adjust) * next_move_modifier * stat_multi) /mob/living/changeNext_move(num) var/mod = next_move_modifier var/adj = next_move_adjust + var/stat_multi = 1 + switch(stat) + if(SOFT_CRIT) + stat_multi = 4 + if(HARD_CRIT) + stat_multi = 8 + else + stat_multi = 1 + for(var/datum/status_effect/effect as anything in status_effects) mod *= effect.nextmove_modifier() adj += effect.nextmove_adjust() - next_move = world.time + ((num + adj)*mod) + next_move = world.time + ((num + adj)*mod * stat_multi) /** * Before anything else, defer these calls to a per-mobtype handler. This allows us to @@ -106,7 +123,7 @@ CtrlClickOn(A) return - if(incapacitated(IGNORE_RESTRAINTS|IGNORE_STASIS)) + if(incapacitated(IGNORE_RESTRAINTS|IGNORE_STASIS|IGNORE_CRIT)) return face_atom(A) @@ -117,7 +134,7 @@ if(!LAZYACCESS(modifiers, "catcher") && A.IsObscured()) return - if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) + if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED) && !((stat >= SOFT_CRIT && (stat != DEAD && stat != UNCONSCIOUS)))) changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow UnarmedAttack(A, FALSE) return diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index aa08d7889fa8..81b4d5179209 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -16,7 +16,7 @@ Otherwise pretty standard. */ /mob/living/carbon/human/UnarmedAttack(atom/A, proximity_flag) - if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) + if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED) && stat < SOFT_CRIT) if(src == A) check_self_for_injuries() return diff --git a/code/controllers/subsystem/ambience.dm b/code/controllers/subsystem/ambience.dm index e138c2d6048c..75d68b1651d5 100644 --- a/code/controllers/subsystem/ambience.dm +++ b/code/controllers/subsystem/ambience.dm @@ -51,6 +51,10 @@ SUBSYSTEM_DEF(ambience) ///Attempts to play an ambient sound to a mob, returning the cooldown in deciseconds /area/proc/play_ambience(mob/M, sound/override_sound, volume = 27) var/sound/new_sound = override_sound || pick(ambientsounds) + if(M.client?.prefs.channel_volume) + volume *= M.client.prefs.channel_volume["[CHANNEL_MASTER_VOLUME]"] * 0.01 + volume *= M.client.prefs.channel_volume["[CHANNEL_AMBIENCE]"] * 0.01 + new_sound = sound(new_sound, repeat = 0, wait = 0, volume = volume, channel = CHANNEL_AMBIENCE) SEND_SOUND(M, new_sound) diff --git a/code/controllers/subsystem/processing/fastprocess.dm b/code/controllers/subsystem/processing/fastprocess.dm index 1b30ca44c240..ce1617253ed9 100644 --- a/code/controllers/subsystem/processing/fastprocess.dm +++ b/code/controllers/subsystem/processing/fastprocess.dm @@ -2,3 +2,9 @@ PROCESSING_SUBSYSTEM_DEF(fastprocess) name = "Fast Processing" wait = 0.2 SECONDS stat_tag = "FP" + +PROCESSING_SUBSYSTEM_DEF(actualfastprocess) + name = "Actual Fast Processing" + wait = 0.1 SECONDS + priority = FIRE_PRIORITY_TICKER + stat_tag = "AFP" diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index c94405a1afa3..1a7196a9eb4c 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -282,7 +282,12 @@ SUBSYSTEM_DEF(ticker) INVOKE_ASYNC(SSdbcore, TYPE_PROC_REF(/datum/controller/subsystem/dbcore,SetRoundStart)) to_chat(world, span_notice("Welcome to [station_name()], enjoy your stay!")) - SEND_SOUND(world, sound(SSstation.announcer.get_rand_welcome_sound())) + + for(var/mob/M as anything in GLOB.player_list) + if(!M.client) + SEND_SOUND(M, sound(SSstation.announcer.get_rand_welcome_sound(), volume = 100)) + else if("[CHANNEL_VOX]" in M.client.prefs.channel_volume) + SEND_SOUND(M, sound(SSstation.announcer.get_rand_welcome_sound(), volume = M.client.prefs.channel_volume["[CHANNEL_VOX]"] * (M.client.prefs.channel_volume["[CHANNEL_MASTER_VOLUME]"] * 0.01))) current_state = GAME_STATE_PLAYING Master.SetRunLevel(RUNLEVEL_GAME) diff --git a/code/datums/announcers/default_announcer.dm b/code/datums/announcers/default_announcer.dm index 9db822e02fef..830471615cbb 100644 --- a/code/datums/announcers/default_announcer.dm +++ b/code/datums/announcers/default_announcer.dm @@ -1,5 +1,7 @@ /datum/centcom_announcer/default - welcome_sounds = list('sound/ai/default/welcome.ogg') + welcome_sounds = list('monkestation/sound/ai/duke/welcome/bonus1.ogg', + 'monkestation/sound/ai/duke/welcome/welcome1.ogg', + 'monkestation/sound/ai/duke/welcome/welcome2.ogg') alert_sounds = list('sound/ai/default/attention.ogg') command_report_sounds = list('sound/ai/default/commandreport.ogg') event_sounds = list(ANNOUNCER_AIMALF = 'sound/ai/default/aimalf.ogg', diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm index 2151a384f62f..226253183825 100644 --- a/code/datums/emotes.dm +++ b/code/datums/emotes.dm @@ -101,7 +101,7 @@ var/tmp_sound = get_sound(user) if(tmp_sound && should_play_sound(user, intentional) && !TIMER_COOLDOWN_CHECK(user, type)) TIMER_COOLDOWN_START(user, type, audio_cooldown) - playsound(user, tmp_sound, 50, vary) + playsound(user, tmp_sound, 50, vary, mixer_channel = CHANNEL_MOB_SOUNDS) var/user_turf = get_turf(user) if (user.client) diff --git a/code/datums/storage/storage.dm b/code/datums/storage/storage.dm index ce0175849090..ace95c63b9a1 100644 --- a/code/datums/storage/storage.dm +++ b/code/datums/storage/storage.dm @@ -983,7 +983,7 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches) resolve_parent.balloon_alert(to_show, "can't reach!") return FALSE - if(!isliving(to_show) || to_show.incapacitated()) + if(!isliving(to_show) || to_show.incapacitated(IGNORE_CRIT)) return FALSE if(locked) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index c2ee234d5857..6373178fb225 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1589,6 +1589,11 @@ pulling.remove_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), CHOKEHOLD_TRAIT) if(. >= GRAB_NECK) // Previous state was a a neck-grab or higher. REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + if(ismob(src)) + var/mob/grabbed = src + if(grabbed.stat == SOFT_CRIT || grabbed.stat == HARD_CRIT) + pulling.add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), CHOKEHOLD_TRAIT) + if(GRAB_AGGRESSIVE) if(. >= GRAB_NECK) // Grab got downgraded. REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) diff --git a/code/game/sound.dm b/code/game/sound.dm index 7e421372cc90..b14a9ee2155b 100644 --- a/code/game/sound.dm +++ b/code/game/sound.dm @@ -1,4 +1,5 @@ GLOBAL_LIST_INIT(used_sound_channels, list( + CHANNEL_MASTER_VOLUME, CHANNEL_LOBBYMUSIC, CHANNEL_ADMIN, CHANNEL_VOX, @@ -145,6 +146,8 @@ GLOBAL_LIST_INIT(proxy_sound_channels, list( sound_to_use.wait = 0 //No queue sound_to_use.channel = channel || SSsounds.random_available_channel() sound_to_use.volume = vol + if("[CHANNEL_MASTER_VOLUME]" in client?.prefs?.channel_volume) + sound_to_use.volume *= client.prefs.channel_volume["[CHANNEL_MASTER_VOLUME]"] * 0.01 if(vary) if(frequency) @@ -246,6 +249,7 @@ GLOBAL_LIST_INIT(proxy_sound_channels, list( if("[CHANNEL_LOBBYMUSIC]" in prefs.channel_volume) if(prefs.channel_volume["[CHANNEL_LOBBYMUSIC]"] != 0) vol *= prefs.channel_volume["[CHANNEL_LOBBYMUSIC]"] * 0.01 + vol *= prefs.channel_volume["[CHANNEL_MASTER_VOLUME]"] * 0.01 if((prefs && (!prefs.read_preference(/datum/preference/toggle/sound_lobby))) || CONFIG_GET(flag/disallow_title_music)) return diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index bbdb6bfbe22f..989e05caf600 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -157,6 +157,8 @@ GLOBAL_PROTECT(href_token) owner.init_verbs() //re-initialize the verb list owner.update_special_keybinds() GLOB.admins |= client + if(!owner.mentor_datum) + owner.mentor_datum_set() try_give_profiling() @@ -170,6 +172,9 @@ GLOBAL_PROTECT(href_token) GLOB.admins -= owner owner.remove_admin_verbs() owner.holder = null + GLOB.mentors -= owner + owner.mentor_datum.owner = null + owner.mentor_datum = null owner = null /// Returns the feedback forum thread for the admin holder's owner, as according to DB. diff --git a/code/modules/escape_menu/home_page.dm b/code/modules/escape_menu/home_page.dm index ad0febfcb20c..8ab6c27aa708 100644 --- a/code/modules/escape_menu/home_page.dm +++ b/code/modules/escape_menu/home_page.dm @@ -28,13 +28,22 @@ CALLBACK(src, PROC_REF(start_redeem)), ) ) + page_holder.give_screen_object( + new /atom/movable/screen/escape_menu/home_button( + null, + src, + "Open Lootbox", + /* offset = */ 3, + CALLBACK(src, PROC_REF(try_open_lootbox)), + ) + ) page_holder.give_screen_object( new /atom/movable/screen/escape_menu/home_button( null, src, "Open Map", - /* offset = */ 3, + /* offset = */ 4, CALLBACK(src, PROC_REF(open_map)), ) ) @@ -43,7 +52,7 @@ null, src, "Admin Help", - /* offset = */ 4, + /* offset = */ 5, ) ) @@ -52,7 +61,7 @@ null, src, "Leave Body", - /* offset = */ 5, + /* offset = */ 6, CALLBACK(src, PROC_REF(open_leave_body)), ) ) @@ -63,6 +72,9 @@ /datum/escape_menu/proc/start_redeem() client?.redeem_code() +/datum/escape_menu/proc/try_open_lootbox() + client?.try_open_or_buy_lootbox() + /datum/escape_menu/proc/open_map() var/redirect = "" switch(SSmapping.config.map_name) diff --git a/code/modules/events/brain_trauma.dm b/code/modules/events/brain_trauma.dm index 40df052b1ecd..de94a0e9c007 100644 --- a/code/modules/events/brain_trauma.dm +++ b/code/modules/events/brain_trauma.dm @@ -1,7 +1,7 @@ /datum/round_event_control/brain_trauma name = "Spontaneous Brain Trauma" typepath = /datum/round_event/brain_trauma - weight = 10 + weight = 0 category = EVENT_CATEGORY_HEALTH description = "A crewmember gains a random trauma." min_wizard_trigger_potency = 2 diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm index 954d7c925e2a..c4f5d4e7e3a2 100755 --- a/code/modules/jobs/job_types/captain.dm +++ b/code/modules/jobs/job_types/captain.dm @@ -11,7 +11,7 @@ supervisors = "Nanotrasen officials and Space Law" req_admin_notify = 1 minimal_player_age = 14 - exp_requirements = 180 + exp_requirements = 1500 exp_required_type = EXP_TYPE_CREW exp_required_type_department = EXP_TYPE_COMMAND exp_granted_type = EXP_TYPE_CREW diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm index bf511ffb4400..787db0725a75 100644 --- a/code/modules/jobs/job_types/chief_engineer.dm +++ b/code/modules/jobs/job_types/chief_engineer.dm @@ -11,7 +11,7 @@ supervisors = SUPERVISOR_CAPTAIN req_admin_notify = 1 minimal_player_age = 7 - exp_requirements = 180 + exp_requirements = 600 exp_required_type = EXP_TYPE_CREW exp_required_type_department = EXP_TYPE_ENGINEERING exp_granted_type = EXP_TYPE_CREW diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm index 6f70c8676505..7ce73d4e7a5e 100644 --- a/code/modules/jobs/job_types/chief_medical_officer.dm +++ b/code/modules/jobs/job_types/chief_medical_officer.dm @@ -11,7 +11,7 @@ supervisors = SUPERVISOR_CAPTAIN req_admin_notify = 1 minimal_player_age = 7 - exp_requirements = 180 + exp_requirements = 300 exp_required_type = EXP_TYPE_CREW exp_required_type_department = EXP_TYPE_MEDICAL exp_granted_type = EXP_TYPE_CREW diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm index 2bc31e1645f8..89c14fdfca72 100644 --- a/code/modules/jobs/job_types/head_of_personnel.dm +++ b/code/modules/jobs/job_types/head_of_personnel.dm @@ -11,7 +11,7 @@ supervisors = SUPERVISOR_HOP req_admin_notify = 1 minimal_player_age = 10 - exp_requirements = 180 + exp_requirements = 1500 exp_required_type = EXP_TYPE_CREW exp_required_type_department = EXP_TYPE_SERVICE exp_granted_type = EXP_TYPE_CREW diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm index e9994ec122e9..4b0d7aaaaa39 100644 --- a/code/modules/jobs/job_types/head_of_security.dm +++ b/code/modules/jobs/job_types/head_of_security.dm @@ -11,7 +11,7 @@ supervisors = SUPERVISOR_CAPTAIN req_admin_notify = 1 minimal_player_age = 14 - exp_requirements = 300 + exp_requirements = 600 exp_required_type = EXP_TYPE_CREW exp_required_type_department = EXP_TYPE_SECURITY exp_granted_type = EXP_TYPE_CREW diff --git a/code/modules/jobs/job_types/quartermaster.dm b/code/modules/jobs/job_types/quartermaster.dm index a5d1ca7b304e..b7a8691668e2 100644 --- a/code/modules/jobs/job_types/quartermaster.dm +++ b/code/modules/jobs/job_types/quartermaster.dm @@ -10,6 +10,7 @@ supervisors = "the head of personnel" minimal_player_age = 7 supervisors = SUPERVISOR_CAPTAIN + exp_requirements = 120 exp_required_type_department = EXP_TYPE_SUPPLY exp_granted_type = EXP_TYPE_CREW config_tag = "QUARTERMASTER" diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm index 4a96bff4f791..f1a1d8135d41 100644 --- a/code/modules/jobs/job_types/research_director.dm +++ b/code/modules/jobs/job_types/research_director.dm @@ -13,7 +13,7 @@ req_admin_notify = 1 minimal_player_age = 7 exp_required_type_department = EXP_TYPE_SCIENCE - exp_requirements = 180 + exp_requirements = 900 exp_required_type = EXP_TYPE_CREW exp_granted_type = EXP_TYPE_CREW config_tag = "RESEARCH_DIRECTOR" diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index bbf767371807..60715d8efd7b 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -187,7 +187,7 @@ return FALSE //nonliving mobs don't have hands /mob/living/put_in_hand_check(obj/item/I) - if(istype(I) && ((mobility_flags & MOBILITY_PICKUP) || (I.item_flags & ABSTRACT)) \ + if(istype(I) && (((mobility_flags & MOBILITY_PICKUP) || ((stat >= SOFT_CRIT && (stat != DEAD && stat != UNCONSCIOUS)))) || (I.item_flags & ABSTRACT)) \ && !(SEND_SIGNAL(src, COMSIG_LIVING_TRY_PUT_IN_HAND, I) & COMPONENT_LIVING_CANT_PUT_IN_HAND)) return TRUE return FALSE diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 34155f199a1f..8f450ea48d2c 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -504,6 +504,9 @@ * * IGNORE_GRAB - mob that is agressively grabbed is not considered incapacitated **/ /mob/living/incapacitated(flags) + if((flags & IGNORE_CRIT) && ((stat >= SOFT_CRIT && (stat != DEAD && stat != UNCONSCIOUS)) && !src.pulledby)) + return FALSE + if(HAS_TRAIT(src, TRAIT_INCAPACITATED)) return TRUE @@ -2117,26 +2120,26 @@ GLOBAL_LIST_EMPTY(fire_appearances) if(CONSCIOUS) if(. >= UNCONSCIOUS) REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT) - remove_traits(list(TRAIT_HANDS_BLOCKED, TRAIT_INCAPACITATED, TRAIT_FLOORED, TRAIT_CRITICAL_CONDITION), STAT_TRAIT) + remove_traits(list(TRAIT_HANDS_BLOCKED, TRAIT_INCAPACITATED, TRAIT_FLOORED, TRAIT_CRITICAL_CONDITION, TRAIT_POOR_AIM), STAT_TRAIT) if(SOFT_CRIT) if(pulledby) ADD_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT) //adding trait sources should come before removing to avoid unnecessary updates if(. >= UNCONSCIOUS) REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_KNOCKEDOUT) - ADD_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) + add_traits(list(TRAIT_CRITICAL_CONDITION, TRAIT_POOR_AIM), STAT_TRAIT) if(UNCONSCIOUS) if(. != HARD_CRIT) become_blind(UNCONSCIOUS_TRAIT) if(health <= crit_threshold && !HAS_TRAIT(src, TRAIT_NOSOFTCRIT)) - ADD_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) + add_traits( list(TRAIT_CRITICAL_CONDITION, TRAIT_POOR_AIM), STAT_TRAIT) else - REMOVE_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) + remove_traits(list(TRAIT_CRITICAL_CONDITION, TRAIT_POOR_AIM), STAT_TRAIT) if(HARD_CRIT) if(. != UNCONSCIOUS) become_blind(UNCONSCIOUS_TRAIT) - ADD_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) + add_traits(list(TRAIT_CRITICAL_CONDITION, TRAIT_POOR_AIM), STAT_TRAIT) if(DEAD) - REMOVE_TRAIT(src, TRAIT_CRITICAL_CONDITION, STAT_TRAIT) + remove_traits(list(TRAIT_CRITICAL_CONDITION, TRAIT_POOR_AIM), STAT_TRAIT) remove_from_alive_mob_list() add_to_dead_mob_list() diff --git a/goon/icons/effects/320x320.dmi b/goon/icons/effects/320x320.dmi new file mode 100644 index 000000000000..289584a301f9 Binary files /dev/null and b/goon/icons/effects/320x320.dmi differ diff --git a/goon/icons/effects/particles.dmi b/goon/icons/effects/particles.dmi new file mode 100644 index 000000000000..285cfaf7fcad Binary files /dev/null and b/goon/icons/effects/particles.dmi differ diff --git a/goon/icons/obj/large_storage.dmi b/goon/icons/obj/large_storage.dmi new file mode 100644 index 000000000000..bb90d68312aa Binary files /dev/null and b/goon/icons/obj/large_storage.dmi differ diff --git a/goon/sounds/misc/openlootcrate.ogg b/goon/sounds/misc/openlootcrate.ogg new file mode 100644 index 000000000000..7d197eb3c696 Binary files /dev/null and b/goon/sounds/misc/openlootcrate.ogg differ diff --git a/goon/sounds/misc/openlootcrate2.ogg b/goon/sounds/misc/openlootcrate2.ogg new file mode 100644 index 000000000000..fb190b610358 Binary files /dev/null and b/goon/sounds/misc/openlootcrate2.ogg differ diff --git a/interface/skin.dmf b/interface/skin.dmf index c6f2a29ad244..8ab3258f9a6b 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -201,15 +201,6 @@ window "infowindow" saved-params = "is-checked" text = "Report Issue" command = "report-issue" - elem "mediapanel" - type = BROWSER - pos = 392,25 - size = 1x1 - anchor1 = -1,-1 - anchor2 = -1,-1 - background-color = none - is-visible = false - saved-params = "" window "outputwindow" elem "outputwindow" @@ -344,6 +335,15 @@ window "statwindow" anchor2 = 100,100 is-visible = false saved-params = "" + elem "mediapanel" + type = BROWSER + pos = 392,25 + size = 1x1 + anchor1 = -1,-1 + anchor2 = -1,-1 + background-color = none + is-visible = false + saved-params = "" window "tgui_say" elem "tgui_say" diff --git a/monkestation/code/game/sound.dm b/monkestation/code/game/sound.dm index c9309e817d9d..072ece63bd35 100644 --- a/monkestation/code/game/sound.dm +++ b/monkestation/code/game/sound.dm @@ -67,7 +67,7 @@ return GLOB.always_state /datum/ui_module/volume_mixer/proc/set_channel_volume(channel, vol, mob/user) - if(channel == CHANNEL_LOBBYMUSIC) + if((channel == CHANNEL_LOBBYMUSIC) || (channel == CHANNEL_MASTER_VOLUME)) if(isnewplayer(user)) user.client.media.update_volume(0.5 + (vol * 0.05)) @@ -77,6 +77,8 @@ /proc/get_channel_name(channel) switch(channel) + if(CHANNEL_MASTER_VOLUME) + return "Master Volume" if(CHANNEL_LOBBYMUSIC) return "Lobby Music" if(CHANNEL_ADMIN) diff --git a/monkestation/code/modules/cassettes/machines/media/media_manager.dm b/monkestation/code/modules/cassettes/machines/media/media_manager.dm index a9cd5565b9d8..3b03bbb1d155 100644 --- a/monkestation/code/modules/cassettes/machines/media/media_manager.dm +++ b/monkestation/code/modules/cassettes/machines/media/media_manager.dm @@ -111,7 +111,7 @@ var/client/owner // Client this is actually running in var/forced=0 // If true, current url overrides area media sources var/playerstyle // Choice of which player plugin to use - var/const/WINDOW_ID = "infowindow.mediapanel" // Which elem in skin.dmf to use + var/const/WINDOW_ID = "statwindow.mediapanel" // Which elem in skin.dmf to use var/balance=0 // do you know what insanity is? Value from -100 to 100 where -100 is left and 100 is right var/signal_synced = 0 //used to check if we have our signal created @@ -178,7 +178,7 @@ targetURL = M.media_url targetStartTime = M.media_start_time - targetVolume = max(0, M.volume - (dist * 0.1)) + targetVolume = max(0, M.volume * (1 - (dist * 0.1))) targetBalance = x_dist //MP_DEBUG("Found audio source: [M.media_url] @ [(world.time - start_time) / 10]s.") @@ -212,7 +212,7 @@ var/dist = get_dist(new_loc, M) var/x_dist = -(new_loc.x - M.x) * 10 - targetVolume = max(0, M.volume - (dist * 0.1)) + targetVolume = max(0, M.volume * (1 - (dist * 0.1))) targetBalance = x_dist push_volume_recalc(targetVolume, targetBalance) diff --git a/monkestation/code/modules/client/preference_savefile.dm b/monkestation/code/modules/client/preference_savefile.dm index 935f289a778c..fcb1b1694154 100644 --- a/monkestation/code/modules/client/preference_savefile.dm +++ b/monkestation/code/modules/client/preference_savefile.dm @@ -22,8 +22,17 @@ loadout = _text2path(loadout) save_loadout[loadout] = entry + var/list/special_save_loadout = SANITIZE_LIST(save_data["special_loadout_list"]) + for(var/loadout in special_save_loadout["unusual"]) + special_save_loadout["unusual"] -= loadout + + if(istext(loadout)) + loadout = _text2num(loadout) + special_save_loadout["unusual"] += loadout + alt_job_titles = save_data["alt_job_titles"] loadout_list = sanitize_loadout_list(save_loadout) + special_loadout_list = special_save_loadout if(needs_update >= 0) update_character_monkestation(needs_update, save_data) // needs_update == savefile_version if we need an update (positive integer) @@ -37,6 +46,7 @@ /// Saves the modular customizations of a character on the savefile /datum/preferences/proc/save_character_monkestation(list/save_data) save_data["loadout_list"] = loadout_list + save_data["special_loadout_list"] = special_loadout_list save_data["modular_version"] = MODULAR_SAVEFILE_VERSION_MAX save_data["alt_job_titles"] = alt_job_titles @@ -44,8 +54,10 @@ write_jobxp_preferences() savefile.set_entry("channel_volume", channel_volume) savefile.set_entry("saved_tokens", saved_tokens) + savefile.set_entry("extra_stat_inventory", extra_stat_inventory) if(token_month) savefile.set_entry("token_month", token_month) + savefile.set_entry("lootboxes_owned", lootboxes_owned) /datum/preferences/proc/load_preferences_monkestation() load_jobxp_preferences() @@ -55,5 +67,9 @@ saved_tokens = savefile.get_entry("saved_tokens", saved_tokens) saved_tokens = SANITIZE_LIST(saved_tokens) + extra_stat_inventory = savefile.get_entry("extra_stat_inventory", extra_stat_inventory) + extra_stat_inventory = SANITIZE_LIST(extra_stat_inventory) + token_month = savefile.get_entry("token_month", token_month) + lootboxes_owned = savefile.get_entry("lootboxes_owned", lootboxes_owned) diff --git a/monkestation/code/modules/client/preferences.dm b/monkestation/code/modules/client/preferences.dm index a266c0a24036..3bdc1c40ee18 100644 --- a/monkestation/code/modules/client/preferences.dm +++ b/monkestation/code/modules/client/preferences.dm @@ -1,7 +1,12 @@ /datum/preferences /// Loadout prefs. Assoc list of [typepaths] to [associated list of item info]. var/list/loadout_list - + ///list of specially handled loadout items as array indexes for the extra_stat_inventory + var/list/special_loadout_list = list( + "unusual" = list(), + "single-use" = list(), + "generic" = list(), + ) var/needs_update = TRUE ///list of all items in inventory @@ -30,3 +35,11 @@ var/list/alt_job_titles = list() /// the month we used our last donator token on var/token_month = 0 + /// these are inventory items that require external data to load correctly + var/list/extra_stat_inventory = list( + "unusual" = list(), + "single-use" = list(), + "generic" = list(), + ) + ///amount of lootboxes owned + var/lootboxes_owned = 0 diff --git a/monkestation/code/modules/loadouts/loadout_middleware.dm b/monkestation/code/modules/loadouts/loadout_middleware.dm index 6869478d9e61..8d1c773b241a 100644 --- a/monkestation/code/modules/loadouts/loadout_middleware.dm +++ b/monkestation/code/modules/loadouts/loadout_middleware.dm @@ -39,6 +39,7 @@ loadout_tabs += list(list("name" = "Toys", "title" = "Toys! ([MAX_ALLOWED_MISC_ITEMS] max)", "contents" = list_to_data(GLOB.loadout_toys))) loadout_tabs += list(list("name" = "Other", "title" = "Backpack Items ([MAX_ALLOWED_MISC_ITEMS] max)", "contents" = list_to_data(GLOB.loadout_pocket_items))) loadout_tabs += list(list("name" = "Effects", "title" = "Unique Effects", "contents" = list_to_data(GLOB.loadout_effects))) + loadout_tabs += list(list("name" = "Unusuals", "title" = "Unusual Hats", "contents" = convert_stored_unusuals_to_data())) return list("loadout_tabs" = loadout_tabs) @@ -49,12 +50,16 @@ var/list/all_selected_paths = list() for(var/path in preferences.loadout_list) all_selected_paths += path + + var/list/all_selected_unusuals = list() + if(length(preferences.special_loadout_list["unusual"])) + all_selected_unusuals = preferences.special_loadout_list["unusual"] + data["selected_loadout"] = all_selected_paths + data["selected_unusuals"] = all_selected_unusuals data["user_is_donator"] = !!(preferences.parent.patreon?.is_donator() || is_admin(preferences.parent)) data["mob_name"] = preferences.read_preference(/datum/preference/name/real_name) data["ismoth"] = istype(preferences.parent.prefs.read_preference(/datum/preference/choiced/species), /datum/species/moth) // Moth's humanflaticcon isn't the same dimensions for some reason - data["preivew_options"] = list(PREVIEW_PREF_JOB, PREVIEW_PREF_LOADOUT, PREVIEW_PREF_NAKED) - data["preview_selection"] = PREVIEW_PREF_JOB data["total_coins"] = preferences.metacoins return data @@ -83,6 +88,10 @@ return interacted_item /datum/preference_middleware/loadout/proc/select_item(list/params, mob/user) + if(params["unusual_spawning_requirements"]) + unusual_selection(params, user) + return + var/datum/loadout_item/interacted_item = return_item(params) if(!interacted_item) return @@ -108,6 +117,20 @@ ui.send_update() preferences.character_preview_view?.update_body() +/datum/preference_middleware/proc/unusual_selection(list/params, mob/user) + if("[params["unusual_placement"]]" in preferences.special_loadout_list["unusual"]) + preferences.special_loadout_list["unusual"] -= params["unusual_placement"] + preferences.save_preferences() + return + + if(!islist(preferences.special_loadout_list["unusual"])) + preferences.special_loadout_list["unusual"] = list() + + preferences.special_loadout_list["unusual"] += "[params["unusual_placement"]]" + var/datum/tgui/ui = SStgui.get_open_ui(user, preferences) + ui.send_update() + preferences.character_preview_view?.update_body() + /// Deselect [deselected_item]. /datum/preference_middleware/proc/deselect_item(datum/loadout_item/deselected_item, mob/user) LAZYREMOVE(preferences.loadout_list, deselected_item.item_path) @@ -157,6 +180,30 @@ return formatted_list +/datum/preference_middleware/proc/convert_stored_unusuals_to_data() + var/list/data = preferences.extra_stat_inventory["unusual"] + if(!length(data)) + return + + var/list/formatted_list = new(length(data)) + + var/array_index = 1 + for(var/iter as anything in data) + var/list/formatted_item = list() + formatted_item["name"] = data[array_index]["name"] + formatted_item["path"] = data[array_index]["unusual_type"] + formatted_item["unusual_placement"] = "[array_index]" + formatted_item["is_greyscale"] = FALSE + formatted_item["is_renamable"] = FALSE + formatted_item["is_job_restricted"] = FALSE + formatted_item["is_donator_only"] = FALSE + formatted_item["is_ckey_whitelisted"] = FALSE + formatted_item["unusual_spawning_requirements"] = TRUE + + formatted_list[array_index++] = formatted_item + + return formatted_list + /datum/preference_middleware/loadout/proc/set_name(list/params, mob/user) var/datum/loadout_item/item = return_item(params) if(!item) @@ -251,6 +298,7 @@ /datum/preference_middleware/loadout/proc/clear_all_items() LAZYNULL(preferences.loadout_list) + preferences.special_loadout_list["unusual"] = list() preferences.character_preview_view.update_body() /datum/preference_middleware/loadout/proc/ckey_explain(list/params, mob/user) diff --git a/monkestation/code/modules/loadouts/loadout_outfit_helper.dm b/monkestation/code/modules/loadouts/loadout_outfit_helper.dm index dc5eecad5abc..703955cd8e23 100644 --- a/monkestation/code/modules/loadouts/loadout_outfit_helper.dm +++ b/monkestation/code/modules/loadouts/loadout_outfit_helper.dm @@ -38,6 +38,7 @@ var/list/loadout_datums = loadout_list_to_datums(preference_source?.loadout_list) + if(override_preference == LOADOUT_OVERRIDE_CASE && !visuals_only) var/obj/item/storage/briefcase/empty/briefcase = new(loc) @@ -48,6 +49,15 @@ continue new item.item_path(briefcase) + var/list/numbers = list() + for(var/num as anything in preference_source?.special_loadout_list["unusual"]) + if(num in numbers) + continue + numbers += text2num(num) + var/list/data = preference_source?.extra_stat_inventory["unusual"][num] + var/item_path = text2path(data["unusual_type"]) + var/obj/item/new_item = new item_path(briefcase) + new_item.AddComponent(/datum/component/unusual_handler, data) briefcase.name = "[preference_source.read_preference(/datum/preference/name/real_name)]'s travel suitcase" equipOutfit(equipped_outfit, visuals_only) @@ -66,6 +76,16 @@ equipOutfit(equipped_outfit, visuals_only) + + for(var/num as anything in preference_source?.special_loadout_list["unusual"]) + var/list/data = preference_source?.extra_stat_inventory["unusual"][text2num(num)] + var/item_path = text2path(data["unusual_type"]) + var/obj/item/new_item = new item_path + new_item.AddComponent(/datum/component/unusual_handler, data) + if(!new_item.equip_to_best_slot(src)) + if(!put_in_hands(new_item)) + new_item.forceMove(get_turf(src)) + for(var/datum/loadout_item/item as anything in loadout_datums) if(istype(item, /datum/loadout_item/effects)) continue diff --git a/monkestation/code/modules/storytellers/converted_events/solo/obsessed.dm b/monkestation/code/modules/storytellers/converted_events/solo/obsessed.dm index e14da489b1e4..f2f6498560eb 100644 --- a/monkestation/code/modules/storytellers/converted_events/solo/obsessed.dm +++ b/monkestation/code/modules/storytellers/converted_events/solo/obsessed.dm @@ -18,7 +18,7 @@ /datum/round_event/antagonist/solo/obsessed -/datum/round_event/antagonist/solo/clockcult/add_datum_to_mind(datum/mind/antag_mind) +/datum/round_event/antagonist/solo/obsessed/add_datum_to_mind(datum/mind/antag_mind) antag_mind.add_antag_datum(antag_datum) var/mob/living/carbon/human/current = antag_mind.current current.gain_trauma(/datum/brain_trauma/special/obsessed) diff --git a/monkestation/code/modules/storytellers/gamemode_subsystem.dm b/monkestation/code/modules/storytellers/gamemode_subsystem.dm index 2aa4db750c88..d615c93dcbc3 100644 --- a/monkestation/code/modules/storytellers/gamemode_subsystem.dm +++ b/monkestation/code/modules/storytellers/gamemode_subsystem.dm @@ -257,6 +257,8 @@ SUBSYSTEM_DEF(gamemode) if(QDELETED(candidate) || !candidate.key || !candidate.client || (!observers && !candidate.mind)) continue if(!observers) + if(!isliving(candidate)) + continue if(no_antags && candidate.mind.special_role) continue if(restricted_roles && (candidate.mind.assigned_role.title in restricted_roles)) diff --git a/monkestation/code/modules/trading/box_rolling.dm b/monkestation/code/modules/trading/box_rolling.dm new file mode 100644 index 000000000000..53144837c7fb --- /dev/null +++ b/monkestation/code/modules/trading/box_rolling.dm @@ -0,0 +1,122 @@ +/atom/movable/screen/fullscreen/lootbox_overlay + icon = 'goon/icons/effects/320x320.dmi' + icon_state = "lootb0" + screen_loc = "CENTER-3, CENTER-3" + mouse_opacity = MOUSE_OPACITY_OPAQUE + plane = HUD_PLANE + show_when_dead = TRUE + +/atom/movable/screen/fullscreen/lootbox_overlay/sparks + icon_state = "sparks" + layer = FULLSCREEN_LAYER + 0.2 + +/atom/movable/screen/fullscreen/lootbox_overlay/background + icon_state = "background" + layer = FULLSCREEN_LAYER + 0.1 + +/atom/movable/screen/fullscreen/lootbox_overlay/item_preview + icon_state = "nuthin" // we set this ourselves + layer = FULLSCREEN_LAYER + 0.3 + screen_loc = "CENTER+1:35, CENTER+2" + +/atom/movable/screen/fullscreen/lootbox_overlay/duplicate + icon_state = "duplicate" + layer = FULLSCREEN_LAYER + 0.4 + plane = ABOVE_HUD_PLANE + +/atom/movable/screen/fullscreen/lootbox_overlay/main + ///have we already opened? prevents spam clicks + var/opened = FALSE + ///are we a guarenteed roll for lootboxes. + var/guarentee_unusual = FALSE + +/atom/movable/screen/fullscreen/lootbox_overlay/main/guaranteed + guarentee_unusual = TRUE + +/atom/movable/screen/fullscreen/lootbox_overlay/main/Click(location, control, params) + if(opened) + return + opened = TRUE + playsound(usr, pick('goon/sounds/misc/openlootcrate.ogg', 'goon/sounds/misc/openlootcrate2.ogg'), 100, 0) + icon_state = "lootb2" + flick("lootb1", src) + addtimer(CALLBACK(src, PROC_REF(after_open), usr), 2 SECONDS) + +/atom/movable/screen/fullscreen/lootbox_overlay/main/proc/after_open(mob/user) + if(!user) // uh + return + + //now we add + user.overlay_fullscreen("lb_spark", /atom/movable/screen/fullscreen/lootbox_overlay/sparks) + user.overlay_fullscreen("lb_bg", /atom/movable/screen/fullscreen/lootbox_overlay/background) + var/atom/movable/screen/fullscreen/lootbox_overlay/item_preview/preview = user.overlay_fullscreen("lb_preview", /atom/movable/screen/fullscreen/lootbox_overlay/item_preview) + + var/type_rolled + if(!guarentee_unusual) + type_rolled = rand(1, 100) + else + type_rolled = 1 + + var/type_string + switch(type_rolled) + if(1) + type_string = "Unusual" + if(2 to 3) + type_string = "High Tier" + if(4 to 8) + type_string = "Medium Tier" + if(9 to 15) + type_string = "Low Tier" + else + type_string = "Loadout Item" + + var/obj/item/rolled_item = return_rolled(type_string, user) + preview.icon_state = rolled_item.icon_state + preview.icon = rolled_item.icon + preview.appearance = rolled_item.appearance + preview.scale_to(10, 10) + user.reload_fullscreen() + preview.plane = ABOVE_HUD_PLANE + + maptext = "[rolled_item.name]" + maptext_width = 360 + maptext_x += 120 - length(rolled_item.name) + maptext_y += 60 + if(user.client) + message_admins("[user.client.ckey] opened a lootbox and recieved [rolled_item.name]!") + log_game("[user.client.ckey] opened a lootbox and recieved [rolled_item.name]!") + preview.filters += filter(type = "drop_shadow", x = 0, y = 0, size= 5, offset = 0, color = "#F0CA85") + if(type_string == "Unusual") + to_chat(world, span_boldannounce("[user] has unboxed an [rolled_item.name]!")) + if(isliving(user) && !user.put_in_hands(rolled_item)) + rolled_item.forceMove(get_turf(user)) + + addtimer(CALLBACK(src, PROC_REF(cleanup), user), 3 SECONDS) + +/atom/movable/screen/fullscreen/lootbox_overlay/main/proc/cleanup(mob/user) + if(!user) + return + user.clear_fullscreen("lb_spark", 1 SECONDS) + user.clear_fullscreen("lb_bg", 1 SECONDS) + user.clear_fullscreen("lb_preview", 1 SECONDS) + user.clear_fullscreen("lb_main", 1 SECONDS) + user.clear_fullscreen("lb_duplicate", 1 SECONDS) + qdel(src) + + +/proc/testing_trigger_lootbox() + var/mob/user = usr + user.overlay_fullscreen("lb_main", /atom/movable/screen/fullscreen/lootbox_overlay/main/guaranteed) + +/mob/proc/trigger_lootbox_on_self() + src.overlay_fullscreen("lb_main", /atom/movable/screen/fullscreen/lootbox_overlay/main) + +/obj/item/lootbox + name = "lootbox" + icon = 'goon/icons/obj/large_storage.dmi' + icon_state = "attachecase-old" + +/obj/item/lootbox/attack_self(mob/user, modifiers) + . = ..() + user.trigger_lootbox_on_self() + qdel(src) diff --git a/monkestation/code/modules/trading/icons/particles.dmi b/monkestation/code/modules/trading/icons/particles.dmi new file mode 100644 index 000000000000..2705144d8e4f Binary files /dev/null and b/monkestation/code/modules/trading/icons/particles.dmi differ diff --git a/monkestation/code/modules/trading/icons/unusual_overlay.dmi b/monkestation/code/modules/trading/icons/unusual_overlay.dmi new file mode 100644 index 000000000000..e163aa7ef88d Binary files /dev/null and b/monkestation/code/modules/trading/icons/unusual_overlay.dmi differ diff --git a/monkestation/code/modules/trading/lootbox_buying.dm b/monkestation/code/modules/trading/lootbox_buying.dm new file mode 100644 index 000000000000..64ee420640bc --- /dev/null +++ b/monkestation/code/modules/trading/lootbox_buying.dm @@ -0,0 +1,52 @@ +/client/proc/try_open_or_buy_lootbox() + if(!prefs) + return + if(!prefs.lootboxes_owned) + buy_lootbox() + if(prefs.lootboxes_owned) + open_lootbox() + +/client/proc/buy_lootbox() + if(!prefs) + return + if(!prefs.has_coins(5000)) + to_chat(src, span_warning("You do not have enough Monkecoins to buy a lootbox")) + return + switch(tgui_alert(src, "Would you like to purchase a lootbox? 5K", "Buy a lootbox!", list("Yes", "No"))) + if("Yes") + attempt_lootbox_buy() + else + return + +/client/proc/attempt_lootbox_buy() + if(!prefs.adjust_metacoins(ckey, -5000, donator_multipler = FALSE)) + return + prefs.lootboxes_owned++ + +/client/proc/open_lootbox() + message_admins("[ckey] opened a lootbox!") + log_game("[ckey] opened a lootbox!") + if(!mob) + return + + if(isnewplayer(mob)) + to_chat(mob, span_warning("Observe or spawn in first!")) + return + + if(!prefs.lootboxes_owned) + return + prefs.lootboxes_owned-- + mob.trigger_lootbox_on_self() + +/proc/give_lootboxes_to_randoms(amount) + for(var/i = 1 to amount) + var/mob/mob = pick(GLOB.player_list) + if(!mob.client) + continue + mob.client.give_lootbox(1) + +/client/proc/give_lootbox(amount) + if(!prefs) + return + prefs.lootboxes_owned += amount + to_chat(mob, span_notice("You have been given [amount] lootboxes! Open it using the escape menu.")) diff --git a/monkestation/code/modules/trading/lootbox_odds.dm b/monkestation/code/modules/trading/lootbox_odds.dm new file mode 100644 index 000000000000..23103913df67 --- /dev/null +++ b/monkestation/code/modules/trading/lootbox_odds.dm @@ -0,0 +1,117 @@ + +//global because its easier long term +//also its own file to make it super easy +//to find and adjust odds of lootbox rolls +/proc/return_rolled(type_string, mob/user) + var/obj/item/temp + switch(type_string) + if("Unusual") + var/list/viable_hats = list( + /obj/item/clothing/head/caphat, + /obj/item/clothing/head/beanie, + /obj/item/clothing/head/beret, + ) + viable_hats += subtypesof(/obj/item/clothing/head/hats) - typesof(/obj/item/clothing/head/hats/hos) - /obj/item/clothing/head/hats/centcom_cap - /obj/item/clothing/head/hats/hopcap - /obj/item/clothing/head/hats/centhat - /obj/item/clothing/head/hats/warden + viable_hats += subtypesof(/obj/item/clothing/head/costume) + var/path = pick(viable_hats) + temp = new path + var/list/viable_unusuals = subtypesof(/datum/component/particle_spewer) - /datum/component/particle_spewer/movement + var/picked_path = pick(viable_unusuals) + var/pulled_key = user.ckey + if(!pulled_key) + pulled_key = "MissingNo." // have fun trying to get this one lol + temp.AddComponent(/datum/component/unusual_handler, particle_path = picked_path, fresh_unusual = TRUE, client_ckey = pulled_key) + + if(user.client?.prefs) + user.client.prefs.save_new_unusual(temp) + + //token adding + if("High Tier") + temp = new /obj/item/coin/antagtoken + temp.name = "High Tier Antag Token" + user.client.saved_tokens.adjust_tokens(HIGH_THREAT, 1) + if("Medium Tier") + temp = new /obj/item/coin/antagtoken + temp.name = "Medium Tier Antag Token" + user.client.saved_tokens.adjust_tokens(MEDIUM_THREAT, 1) + if("Low Tier") + temp = new /obj/item/coin/antagtoken + temp.name = "Low Tier Antag Token" + user.client.saved_tokens.adjust_tokens(LOW_THREAT, 1) + + if("Loadout Item") + var/static/list/viable_types = list() + if(!length(viable_types)) + for(var/datum/loadout_item/type as anything in subtypesof(/datum/loadout_item)) + var/datum/loadout_item/listed = new type() + if(!istype(listed)) + continue + if(!listed.requires_purchase || listed.donator_only) + continue + if(!listed.item_path) + continue + if(length(listed.ckeywhitelist)) + continue + viable_types += listed + var/datum/loadout_item/picked = pick(viable_types) + temp = new picked.item_path + if(picked.item_path in user.client.prefs.inventory) + user.client.prefs.adjust_metacoins(user.ckey, 2500, "Duplicate Loadout Item", donator_multipler = FALSE) + temp.color = COLOR_GRAY + temp.name = "Loadout Item [temp.name] (Duplicate)" + user.overlay_fullscreen("lb_duplicate", /atom/movable/screen/fullscreen/lootbox_overlay/duplicate) + else + picked.add_to_user(usr.client) + temp.name = "Loadout Item [temp.name]" + + return temp + + +/datum/loadout_item/proc/add_to_user(client/buyer) + SHOULD_CALL_PARENT(TRUE) + var/fail_message ="Failed to add lootbox item to database. Will reattempt until added!" + if(!SSdbcore.IsConnected()) + to_chat(buyer, fail_message) + return FALSE + if(!buyer?.prefs) + return FALSE + if(!buyer.prefs.inventory[item_path]) + buyer.prefs.inventory += item_path + var/datum/db_query/query_add_gear_purchase = SSdbcore.NewQuery({" + INSERT INTO [format_table_name("metacoin_item_purchases")] (`ckey`, `item_id`, `amount`) VALUES (:ckey, :item_id, :amount)"}, + list("ckey" = buyer.ckey, "item_id" = item_path, "amount" = 1)) + if(!query_add_gear_purchase.Execute()) + to_chat(buyer, fail_message) + qdel(query_add_gear_purchase) + addtimer(CALLBACK(src, PROC_REF(add_to_user), buyer), 15 SECONDS) + return FALSE + qdel(query_add_gear_purchase) + else + buyer.prefs.inventory += item_path + var/datum/db_query/query_add_gear_purchase = SSdbcore.NewQuery({" + UPDATE [format_table_name("metacoin_item_purchases")] SET amount = :amount WHERE ckey = :ckey AND item_id = :item_id"}, + list("ckey" = buyer.ckey, "item_id" = item_path, "amount" = 1)) + if(!query_add_gear_purchase.Execute()) + to_chat(buyer, fail_message) + qdel(query_add_gear_purchase) + return FALSE + qdel(query_add_gear_purchase) + + return TRUE + +/proc/testing_spawn_bulk_unusuals() + var/turf/turf = get_turf(usr) + + for(var/i = 1 to 20) + var/list/viable_hats = list( + /obj/item/clothing/head/caphat, + /obj/item/clothing/head/beanie, + /obj/item/clothing/head/beret, + ) + viable_hats += subtypesof(/obj/item/clothing/head/hats) - typesof(/obj/item/clothing/head/hats/hos) - /obj/item/clothing/head/hats/centcom_cap - /obj/item/clothing/head/hats/hopcap - /obj/item/clothing/head/hats/centhat - /obj/item/clothing/head/hats/warden + viable_hats += subtypesof(/obj/item/clothing/head/costume) - /obj/item/clothing/head/costume/nightcap + var/path = pick(viable_hats) + var/obj/item/temp = new path(turf) + var/list/viable_unusuals = subtypesof(/datum/component/particle_spewer) - /datum/component/particle_spewer/movement + var/picked_path = pick(viable_unusuals) + temp.AddComponent(/datum/component/unusual_handler, particle_path = picked_path) diff --git a/monkestation/code/modules/trading/readme.md b/monkestation/code/modules/trading/readme.md new file mode 100644 index 000000000000..84186d0fc842 --- /dev/null +++ b/monkestation/code/modules/trading/readme.md @@ -0,0 +1,24 @@ +## Title: + +MODULE ID: TRADING + +### Description: + +Adds in auction house and surrounding systems + +### TG Proc Changes: + +### Defines: + +N/A + +### Master file additions + +N/A + +### Included files that are not contained in this module: + +N/A + +### Credits: +Dwasint diff --git a/monkestation/code/modules/trading/save_unusual_preference.dm b/monkestation/code/modules/trading/save_unusual_preference.dm new file mode 100644 index 000000000000..b2818a69525a --- /dev/null +++ b/monkestation/code/modules/trading/save_unusual_preference.dm @@ -0,0 +1,44 @@ +/datum/preferences/proc/save_new_unusual(obj/item/unusual) + var/datum/component/unusual_handler/component = unusual.GetComponent(/datum/component/unusual_handler) + if(!component) + return + + var/list/data = list() + // These MUST be strings if you don't make them a string whatever daemon lurks inside of byond will get you. + data["name"] = unusual.name + data["type"] = "[component.particle_path]" + data["round"] = "[component.round_id]" + data["original_owner"] = component.original_owner_ckey + data["description"] = component.unusual_description + data["equipslot"] = "[component.unusual_equip_slot]" + data["item_overlay"] = component.unusal_overlay + data["unusual_type"] = "[unusual.type]" + data["unusual_number"] = "[component.unusual_number]" + + extra_stat_inventory["unusual"] += list(data) + save_preferences() + +/datum/preferences/proc/return_unusual_data(number) + return extra_stat_inventory["unusual"][number] + + +/datum/preferences/proc/clear_unusuals() + extra_stat_inventory["unusual"] = list() + save_preferences() + +/mob/proc/spawn_first_stored_unusual() + if(!client?.prefs) + return + var/list/data = client.prefs.return_unusual_data(1) + var/item_path = text2path(data["unusual_type"]) + var/obj/item/new_item = new item_path(get_turf(src)) + + new_item.AddComponent(/datum/component/unusual_handler, data) + +/mob/proc/create_unusual() + if(!client?.prefs) + return + var/obj/item/clothing/head/costume/nightcap/red/created = new() + + created.AddComponent(/datum/component/unusual_handler, particle_path = /datum/component/particle_spewer/fire, fresh_unusual = TRUE, client_ckey = ckey) + client.prefs.save_new_unusual(created) diff --git a/monkestation/code/modules/trading/unusual_effects/_unusual_component.dm b/monkestation/code/modules/trading/unusual_effects/_unusual_component.dm new file mode 100644 index 000000000000..c086f9421291 --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/_unusual_component.dm @@ -0,0 +1,109 @@ +GLOBAL_LIST_INIT(total_unusuals_per_type, list()) + + +/datum/component/unusual_handler + var/atom/source_object + ///the description added to the unusual. + var/unusual_description = "Not Implemented Yet Teehee" + ///the round the unusual was created at + var/round_id = 0 + ///the particle spewer component path + var/particle_path = /datum/component/particle_spewer/confetti + /// The original owners name + var/original_owner_ckey = "dwasint" + /// the slot this item goes in used when creating the particle itself + var/unusual_equip_slot = ITEM_SLOT_HEAD + /// the icon_state of the overlay given to unusals + var/unusal_overlay = "none" + ///the unique number our unusual is in the item path + var/unusual_number = 0 + +//this init is handled far differently than others. it parses data from the DB for information about the unusual itself +//it than loads this info into the component itself, the particle_path is purely for spawning temporary ones in round +/datum/component/unusual_handler/Initialize(list/parsed_variables = list(), particle_path = /datum/component/particle_spewer/confetti, fresh_unusual = FALSE, client_ckey = "dwasint") + . = ..() + source_object = parent + if(!length(GLOB.total_unusuals_per_type)) + fetch_unusual_data() + + if(!length(parsed_variables)) + src.particle_path = particle_path + else + setup_from_list(parsed_variables) + + if(fresh_unusual) + original_owner_ckey = client_ckey + round_id = text2num(GLOB.round_id) + GLOB.total_unusuals_per_type["[particle_path]"]++ + unusual_number = "[GLOB.total_unusuals_per_type["[particle_path]"]]" + + + source_object.AddComponent(src.particle_path) + + if(!length(parsed_variables)) + var/datum/component/particle_spewer/created = source_object.GetComponent(/datum/component/particle_spewer) + unusual_description = created.unusual_description + + source_object.desc += span_notice("\n Unboxed by: [original_owner_ckey]") + source_object.desc += span_notice("\n Unboxed on round: [round_id]") + source_object.desc += span_notice("\n Unusual Type: [unusual_description]") + source_object.desc += span_notice("\n Series Number: [unusual_number]") + + if(!length(parsed_variables)) + switch(unusual_number) + if(1) + source_object.name = span_hypnophrase("unusual [unusual_description] [source_object.name]") + if(2 to 5) + source_object.name = span_cult("unusual [unusual_description] [source_object.name]") + if(6 to 10) + source_object.name = span_clown("unusual [unusual_description] [source_object.name]") + if(11 to 25) + source_object.name = span_green("unusual [unusual_description] [source_object.name]") + else + source_object.name = "unusual [unusual_description] [source_object.name]" + + RegisterSignal(source_object, COMSIG_ATOM_UPDATE_DESC, PROC_REF(append_unusual)) + save_unusual_data() + +/datum/component/unusual_handler/Destroy(force, silent) + . = ..() + UnregisterSignal(source_object, COMSIG_ATOM_UPDATE_DESC) + +/datum/component/unusual_handler/proc/append_unusual(atom/source, updates) + SIGNAL_HANDLER + source_object.desc = initial(source_object.desc) + source_object.desc += span_notice("\n Unboxed by: [original_owner_ckey]") + source_object.desc += span_notice("\n Unboxed on: [round_id]") + source_object.desc += span_notice("\n Unusual Type: [unusual_description]") + source_object.desc += span_notice("\n Series Number: [unusual_number]") + +/datum/component/unusual_handler/proc/setup_from_list(list/parsed_results) + particle_path = text2path(parsed_results["type"]) + round_id = text2num(parsed_results["round"]) + original_owner_ckey = parsed_results["original_owner"] + unusual_description = parsed_results["description"] + unusual_equip_slot = text2num(parsed_results["equipslot"]) + unusal_overlay = parsed_results["item_overlay"] + unusual_number = parsed_results["unusual_number"] + source_object.name = parsed_results["name"] + +/datum/component/unusual_handler/proc/fetch_unusual_data() + var/json_file = file("data/unusual_tracking.json") + if(!fexists(json_file)) + stack_trace("We are missing the unusual JSON file, this will mess up unusual counting and unique names!") + var/list/json = json_decode(file2text(json_file)) + + if(!json) + return + + for(var/type in json) + GLOB.total_unusuals_per_type[type] = json[type] + +/datum/component/unusual_handler/proc/save_unusual_data() + var/json_file = file("data/unusual_tracking.json") + if(!fexists(json_file)) + stack_trace("We are missing the unusual JSON file, this will mess up unusual counting and unique names!") + fdel(json_file) + WRITE_FILE(json_file, json_encode(GLOB.total_unusuals_per_type)) + + diff --git a/monkestation/code/modules/trading/unusual_effects/animation_housing/__spawning_component.dm b/monkestation/code/modules/trading/unusual_effects/animation_housing/__spawning_component.dm new file mode 100644 index 000000000000..e59071855d02 --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/animation_housing/__spawning_component.dm @@ -0,0 +1,168 @@ + +/obj/effect/abstract/particle + name = "" + plane = GAME_PLANE_FOV_HIDDEN + appearance_flags = RESET_ALPHA | RESET_COLOR | RESET_TRANSFORM | KEEP_APART | TILE_BOUND + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + icon = 'monkestation/code/modules/trading/icons/particles.dmi' + icon_state = "none" + +/datum/component/particle_spewer + var/atom/source_object + ///the unusual_description grabbed into the actual handler itself only needed when used as an unusual + var/unusual_description = "teehee" + //the worn mob + var/mob/worn_mob + ///the duration we last + var/duration = 0 + ///the spawn intervals in game ticks + var/spawn_interval = 1 + ///particles still in the process of animating + var/list/living_particles = list() + ///list of particles that finished (added only as a failsafe) + var/list/dead_particles = list() + ///x offset for source_object + var/offset_x = 0 + ///y offset for source_object + var/offset_y = 0 + ///the dmi location of the particle + var/icon_file = 'monkestation/code/modules/trading/icons/particles.dmi' + ///the icon_state given to the objects + var/particle_state = "none" + ///current process count + var/count = 0 + ///equipped offset ie hats go to 32 if set to 32 will also reset to height changes + var/equipped_offset = 0 + ///per burst spawn amount + var/burst_amount = 1 + ///the actual lifetime of this component before we die [ 0 = infinite] + var/lifetime = 0 + ///kept track of for removal sake + var/added_x = 0 + var/added_y = 0 + /// do we do random amounts of particle bursts? + var/random_bursts = FALSE + ///should we offset + var/offsets = TRUE + /// do we process? + var/processes = TRUE + ///the blend type we use for particles + var/particle_blending = BLEND_DEFAULT + +/datum/component/particle_spewer/Initialize(duration = 0, spawn_interval = 0, offset_x = 0, offset_y = 0, icon_file, particle_state, equipped_offset = 0, burst_amount = 0, lifetime = 0, random_bursts = 0) + . = ..() + if(icon_file) + src.icon_file = icon_file + if(particle_state) + src.particle_state = particle_state + if(offset_x) + src.offset_x = offset_x + rand(-8, 8) + if(offset_y) + src.offset_y = offset_y + rand(-4, 4) + if(spawn_interval) + src.spawn_interval = spawn_interval + if(duration) + src.duration = duration + if(equipped_offset) + src.equipped_offset = equipped_offset + if(burst_amount) + src.burst_amount = burst_amount + if(lifetime) + src.lifetime = lifetime + if(random_bursts) + src.random_bursts = random_bursts + source_object = parent + + if(processes) + START_PROCESSING(SSactualfastprocess, src) + RegisterSignal(source_object, COMSIG_ITEM_EQUIPPED, PROC_REF(handle_equip_offsets)) + RegisterSignal(source_object, COMSIG_ITEM_POST_UNEQUIP, PROC_REF(reset_offsets)) + + if(lifetime) + addtimer(CALLBACK(src, PROC_REF(kill_it_with_fire)), lifetime) + +/datum/component/particle_spewer/Destroy(force, silent) + . = ..() + UnregisterSignal(source_object, list( + COMSIG_ITEM_EQUIPPED, + COMSIG_ITEM_POST_UNEQUIP, + )) + + STOP_PROCESSING(SSactualfastprocess, src) + for(var/atom/listed_atom as anything in living_particles + dead_particles) + qdel(listed_atom) + living_particles = null + dead_particles = null + source_object = null + +/datum/component/particle_spewer/process(seconds_per_tick) + if(spawn_interval != 1) + count++ + if(count < spawn_interval) + return + count = 0 + spawn_particles() + +/datum/component/particle_spewer/proc/spawn_particles(atom/movable/mover, turf/target) + var/burstees = burst_amount + if(random_bursts) + burstees = rand(1, burst_amount) + + for(var/i = 0 to burstees) + //create and assign particle its stuff + var/obj/effect/abstract/particle/spawned = new(get_turf(source_object)) + if(offsets) + spawned.pixel_x = offset_x + spawned.pixel_y = offset_y + spawned.icon = icon_file + spawned.icon_state = particle_state + spawned.blend_mode = particle_blending + + living_particles |= spawned + + animate_particle(spawned) + +///this is the proc that gets overridden when we create new particle spewers that control its movements +//example is animating upwards over duration and deleting +/datum/component/particle_spewer/proc/animate_particle(obj/effect/abstract/particle/spawned) + animate(spawned, alpha = 75, time = duration) + animate(spawned, pixel_y = offset_y + 64, time = duration) + addtimer(CALLBACK(src, PROC_REF(delete_particle), spawned), duration) + +/datum/component/particle_spewer/proc/delete_particle(obj/effect/abstract/particle/spawned) + living_particles -= spawned + qdel(spawned) + +/datum/component/particle_spewer/proc/kill_it_with_fire() + qdel(src) + +/datum/component/particle_spewer/proc/handle_equip_offsets(datum/source, mob/equipper, slot) + SIGNAL_HANDLER + + offset_x -= added_x + offset_y -= added_y + added_x = 0 + added_y = 0 + worn_mob = equipper + + switch(slot) + if(ITEM_SLOT_HEAD) + added_y = 16 + else + added_y = 0 + added_x = 0 + + offset_y += added_y + offset_x += added_x + +/datum/component/particle_spewer/proc/reset_offsets() + SIGNAL_HANDLER + offset_x -= added_x + offset_y -= added_y + added_x = 0 + added_y = 0 + worn_mob = null + +/obj/item/debug_particle_holder/Initialize(mapload) + . = ..() + AddComponent(/datum/component/particle_spewer, 2 SECONDS) diff --git a/monkestation/code/modules/trading/unusual_effects/animation_housing/_footprint.dm b/monkestation/code/modules/trading/unusual_effects/animation_housing/_footprint.dm new file mode 100644 index 000000000000..dec84bd40657 --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/animation_housing/_footprint.dm @@ -0,0 +1,29 @@ +//a unique subtype of particle spewer that only runs on equip +/datum/component/particle_spewer/movement + processes = FALSE + var/mob/attached_signal + +/datum/component/particle_spewer/movement/Destroy(force, silent) + UnregisterSignal(source_object, COMSIG_MOVABLE_PRE_MOVE) + . = ..() + UnregisterSignal(attached_signal, COMSIG_MOVABLE_PRE_MOVE) + attached_signal = null + +/datum/component/particle_spewer/movement/Initialize(duration, spawn_interval, offset_x, offset_y, icon_file, particle_state, equipped_offset, burst_amount, lifetime, random_bursts) + . = ..() + RegisterSignal(source_object, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(spawn_particles)) + +/datum/component/particle_spewer/movement/handle_equip_offsets(datum/source, mob/equipper, slot) + . = ..() + if(attached_signal) + UnregisterSignal(attached_signal, COMSIG_MOVABLE_PRE_MOVE) + attached_signal = null + + attached_signal = equipper + RegisterSignal(equipper, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(spawn_particles)) + +/datum/component/particle_spewer/movement/reset_offsets() + . = ..() + if(attached_signal) + UnregisterSignal(attached_signal, COMSIG_MOVABLE_PRE_MOVE) + attached_signal = null diff --git a/monkestation/code/modules/trading/unusual_effects/animation_housing/confetti.dm b/monkestation/code/modules/trading/unusual_effects/animation_housing/confetti.dm new file mode 100644 index 000000000000..ea7ce46d9ddc --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/animation_housing/confetti.dm @@ -0,0 +1,27 @@ +/datum/component/particle_spewer/confetti + unusual_description = "partytime" + duration = 2 SECONDS + burst_amount = 5 + particle_blending = BLEND_ADD + spawn_interval = 1 SECONDS + +/datum/component/particle_spewer/confetti/animate_particle(obj/effect/abstract/particle/spawned) + var/matrix/first = matrix() + var/matrix/second = matrix() + + spawned.pixel_x += rand(-3,3) + spawned.pixel_y += rand(-3,3) + + first.Turn(rand(-90, 90)) + first.Scale(0.5,0.5) + second.Turn(rand(-90, 90)) + + spawned.color = rgb(rand(1, 255), rand(1, 255), rand(1, 255)) + + animate(spawned, transform = first, time = 0.4 SECONDS, pixel_y = rand(-32, 32) + spawned.pixel_y, pixel_x = rand(-32, 32) + spawned.pixel_x, easing = LINEAR_EASING) + animate(transform = second, time = 0.5 SECONDS, alpha = 0, pixel_y = spawned.pixel_y - 5, easing = LINEAR_EASING|EASE_OUT) + addtimer(CALLBACK(src, PROC_REF(delete_particle), spawned), duration) + +/obj/item/debug_confetti/Initialize(mapload) + . = ..() + AddComponent(/datum/component/particle_spewer/confetti) diff --git a/monkestation/code/modules/trading/unusual_effects/animation_housing/fire.dm b/monkestation/code/modules/trading/unusual_effects/animation_housing/fire.dm new file mode 100644 index 000000000000..34f110a76ea7 --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/animation_housing/fire.dm @@ -0,0 +1,29 @@ +/datum/component/particle_spewer/fire + unusual_description = "flaming" + duration = 2 SECONDS + burst_amount = 3 + spawn_interval = 0.2 SECONDS + particle_state = "1x1" + particle_blending = BLEND_ADD + +/datum/component/particle_spewer/fire/animate_particle(obj/effect/abstract/particle/spawned) + spawned.pixel_x += rand(-6,6) + spawned.pixel_y += rand(-4,4) + + spawned.add_filter("outline", 1, list(type = "outline", size = 1, color = "#FF3300")) + spawned.add_filter("bloom", 2 , list(type = "bloom", threshold = rgb(255,128,255), size = 5, offset = 4, alpha = 255)) + + if(prob(35)) + spawned.layer = ABOVE_MOB_LAYER + + var/normal_x = rand(-4, 4) + spawned.pixel_x + var/inverse_x = 0 - normal_x + spawned.alpha = 130 + + animate(spawned, alpha = 255, time = 0.4 SECONDS, pixel_y = rand(6, 16) + spawned.pixel_y, pixel_x = normal_x, easing = LINEAR_EASING) + animate(time = 0.5 SECONDS, alpha = 0, inverse_x , pixel_y = rand(6, 16) + spawned.pixel_y, easing = LINEAR_EASING|EASE_OUT) + addtimer(CALLBACK(src, PROC_REF(delete_particle), spawned), duration) + +/obj/item/debug_fire/Initialize(mapload) + . = ..() + AddComponent(/datum/component/particle_spewer/fire) diff --git a/monkestation/code/modules/trading/unusual_effects/animation_housing/galaxies.dm b/monkestation/code/modules/trading/unusual_effects/animation_housing/galaxies.dm new file mode 100644 index 000000000000..86ae92317b1a --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/animation_housing/galaxies.dm @@ -0,0 +1,28 @@ +/datum/component/particle_spewer/movement/galaxies + unusual_description = "galactic" + duration = 5 SECONDS + spawn_interval = 0.5 SECONDS + burst_amount = 6 + particle_state = "snow_small" + + +/datum/component/particle_spewer/galaxies/animate_particle(obj/effect/abstract/particle/spawned) + var/can_be_shooting = TRUE + if(prob(10)) + spawned.icon_state = "moon" + can_be_shooting = FALSE + + if(prob(10) && can_be_shooting) + spawned.icon_state = "ringed_planet" + can_be_shooting = FALSE + + spawned.pixel_x += rand(-16,16) + spawned.pixel_y += rand(-12,4) + + if(prob(45) && can_be_shooting) + spawned.layer = ABOVE_MOB_LAYER + + animate(spawned, alpha = 0, time = duration) + if(prob(33) && can_be_shooting) + animate(spawned, pixel_y = spawned.pixel_y + rand(-6, 6), pixel_x = spawned.pixel_x + rand(-16, 16), time = 1 SECONDS) + addtimer(CALLBACK(src, PROC_REF(delete_particle), spawned), duration) diff --git a/monkestation/code/modules/trading/unusual_effects/animation_housing/holy_steps.dm b/monkestation/code/modules/trading/unusual_effects/animation_housing/holy_steps.dm new file mode 100644 index 000000000000..456c920bebcf --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/animation_housing/holy_steps.dm @@ -0,0 +1,25 @@ +/datum/component/particle_spewer/movement/holy_steps + unusual_description = "holy treads" + duration = 5 SECONDS + burst_amount = 25 + icon_file = 'goon/icons/effects/particles.dmi' + particle_state = "starsmall" + +/datum/component/particle_spewer/movement/holy_steps/animate_particle(obj/effect/abstract/particle/spawned) + var/matrix/first = matrix() + var/matrix/second = matrix() + + spawned.blend_mode = BLEND_ADD + spawned.pixel_x += rand(-3,3) + spawned.pixel_y += rand(-3,3) + + first.Turn(rand(-90, 90)) + first.Scale(0.5,0.5) + second.Turn(rand(-90, 90)) + + spawned.color = rgb(rand(1, 255), rand(1, 255), rand(1, 255)) + + animate(spawned, transform = first, time = 0.4 SECONDS, pixel_y = rand(-32, 32) + spawned.pixel_y, pixel_x = rand(-32, 32) + spawned.pixel_x, easing = LINEAR_EASING) + animate(transform = second, time = 0.5 SECONDS, pixel_y = spawned.pixel_y - 5, easing = LINEAR_EASING|EASE_OUT) + animate(spawned, alpha = 0, time = duration) + addtimer(CALLBACK(src, PROC_REF(delete_particle), spawned), duration) diff --git a/monkestation/code/modules/trading/unusual_effects/animation_housing/music.dm b/monkestation/code/modules/trading/unusual_effects/animation_housing/music.dm new file mode 100644 index 000000000000..3b3280799a59 --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/animation_housing/music.dm @@ -0,0 +1,33 @@ +/datum/component/particle_spewer/shooting_star + icon_file = 'goon/icons/effects/particles.dmi' + particle_state = "beamed_eighth" + + unusual_description = "melody" + duration = 2.5 SECONDS + burst_amount = 2 + spawn_interval = 0.5 SECONDS + offsets = FALSE + +/datum/component/particle_spewer/shooting_star/animate_particle(obj/effect/abstract/particle/spawned) + var/matrix/first = matrix() + var/matrix/second = matrix() + var/matrix/default = matrix() + + if(prob(30)) + spawned.icon_state = "eighth" + if(prob(25)) + spawned.icon_state = "quarter" + + spawned.pixel_x += rand(-24, 24) + spawned.pixel_y += rand(-6, 6) + first.Turn(rand(-90, 90)) + spawned.transform = first + + second = first + second.Scale(4,4) + second.Turn(rand(-90, 90)) + + animate(spawned, transform = second, time = 1, alpha = 220) + animate(transform = default, time = duration + rand(-5, 5), pixel_y = spawned.pixel_y + 32, alpha = 1) + + addtimer(CALLBACK(src, PROC_REF(delete_particle), spawned), duration + 0.6 SECONDS) diff --git a/monkestation/code/modules/trading/unusual_effects/animation_housing/shooting_stars.dm b/monkestation/code/modules/trading/unusual_effects/animation_housing/shooting_stars.dm new file mode 100644 index 000000000000..44f2a241a8af --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/animation_housing/shooting_stars.dm @@ -0,0 +1,37 @@ +/datum/component/particle_spewer/movement/shooting_star + icon_file = 'goon/icons/effects/particles.dmi' + particle_state = "starsmall" + + unusual_description = "shooting star" + duration = 5 SECONDS + burst_amount = 5 + offsets = FALSE + //has a chance to randomly change on animate + var/direction = NORTH + +/datum/component/particle_spewer/movement/shooting_star/spawn_particles(atom/movable/mover, turf/target) + . = ..() + var/dir = get_dir(mover, target) + direction = dir + +/datum/component/particle_spewer/movement/shooting_star/animate_particle(obj/effect/abstract/particle/spawned) + + spawned.dir = direction + if(prob(30)) + spawned.icon_state = "starlarge" + if(direction & NORTH|SOUTH) + spawned.pixel_x += rand(-16, 16) + + if(direction & EAST|WEST) + spawned.pixel_y += rand(-16, 16) + + switch(direction) + if(NORTH) + animate(spawned, time = rand(0.5 SECONDS, duration), pixel_y = spawned.pixel_y + 160, alpha = 25) + if(SOUTH) + animate(spawned, time = rand(0.5 SECONDS, duration), pixel_y = spawned.pixel_y - 160, alpha = 25) + if(EAST) + animate(spawned, time = rand(0.5 SECONDS, duration), pixel_x = spawned.pixel_x + 160, alpha = 25) + if(WEST) + animate(spawned, time = rand(0.5 SECONDS, duration), pixel_x = spawned.pixel_x - 160, alpha = 25) + addtimer(CALLBACK(src, PROC_REF(delete_particle), spawned), duration) diff --git a/monkestation/code/modules/trading/unusual_effects/animation_housing/skull_rain.dm b/monkestation/code/modules/trading/unusual_effects/animation_housing/skull_rain.dm new file mode 100644 index 000000000000..a65edddd53bc --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/animation_housing/skull_rain.dm @@ -0,0 +1,34 @@ +/datum/component/particle_spewer/movement/skull_rain + unusual_description = "spooky" + icon_file = 'goon/icons/effects/particles.dmi' + particle_state = "skull3" + burst_amount = 4 + duration = 2 SECONDS + random_bursts = TRUE + spawn_interval = 0.4 SECONDS + +/datum/component/particle_spewer/movement/skull_rain/animate_particle(obj/effect/abstract/particle/spawned) + var/matrix/first = matrix(rand(1, 60), MATRIX_ROTATE) + var/matrix/second = matrix() + second.Turn(rand(-60, 60)) + + var/chance = rand(1, 6) + switch(chance) + if(1 to 2) + spawned.icon_state = "skull3" + if(3 to 4) + spawned.icon_state = "skull2" + if(5 to 6) + spawned.icon_state = "skull1" + + if(prob(35)) + spawned.layer = ABOVE_MOB_LAYER + spawned.pixel_x += rand(-12, 12) + spawned.pixel_y += rand(5, 10) + spawned.transform = first + spawned.alpha = 10 + + animate(spawned, transform = second, time = 20, pixel_y = rand(-16, -12), alpha = 255, easing = BOUNCE_EASING) + animate(time = duration, alpha = 1, easing = LINEAR_EASING) + + addtimer(CALLBACK(src, PROC_REF(delete_particle), spawned), duration) diff --git a/monkestation/code/modules/trading/unusual_effects/animation_housing/snow.dm b/monkestation/code/modules/trading/unusual_effects/animation_housing/snow.dm new file mode 100644 index 000000000000..41dd9aeea3cd --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/animation_housing/snow.dm @@ -0,0 +1,29 @@ +/datum/component/particle_spewer/snow + unusual_description = "snowstorm" + icon_file = 'monkestation/code/modules/outdoors/icons/effects/particles/particle.dmi' + particle_state = "cross" + burst_amount = 8 + duration = 2 SECONDS + random_bursts = TRUE + spawn_interval = 0.3 SECONDS + +/datum/component/particle_spewer/snow/animate_particle(obj/effect/abstract/particle/spawned) + var/chance = rand(1, 10) + switch(chance) + if(1 to 2) + spawned.icon_state = "cross" + if(3 to 4) + spawned.icon_state = "snow_2" + if(5 to 6) + spawned.icon_state = "snow_3" + else + spawned.icon_state = "snow_1" + + if(prob(35)) + spawned.layer = ABOVE_MOB_LAYER + spawned.pixel_x += rand(-12, 12) + spawned.pixel_y += rand(-5, 5) + + animate(spawned, pixel_y = spawned.pixel_y - 32, time = 2 SECONDS) + animate(spawned, alpha = 25, time = 1.5 SECONDS) + addtimer(CALLBACK(src, PROC_REF(delete_particle), spawned), duration) diff --git a/monkestation/code/modules/trading/unusual_effects/animation_housing/weh.dm b/monkestation/code/modules/trading/unusual_effects/animation_housing/weh.dm new file mode 100644 index 000000000000..68856f587825 --- /dev/null +++ b/monkestation/code/modules/trading/unusual_effects/animation_housing/weh.dm @@ -0,0 +1,25 @@ +/datum/component/particle_spewer/movement/weh + unusual_description = "weh" + duration = 5 SECONDS + burst_amount = 3 + + particle_state = "map_plushie_lizard" + icon_file = 'icons/obj/toys/plushes.dmi' + +/datum/component/particle_spewer/movement/weh/animate_particle(obj/effect/abstract/particle/spawned) + var/matrix/first = matrix() + var/matrix/second = matrix() + + spawned.pixel_x += rand(-3,3) + spawned.pixel_y += rand(-3,3) + + first.Turn(rand(-90, 90)) + first.Scale(0.5,0.5) + second.Turn(rand(-90, 90)) + + spawned.color = rgb(rand(1, 255), rand(1, 255), rand(1, 255)) + + animate(spawned, transform = first, time = 0.4 SECONDS, pixel_y = rand(-1, 12) + spawned.pixel_y, pixel_x = rand(-32, 32) + spawned.pixel_x, easing = JUMP_EASING) + animate(transform = second, time = 0.5 SECONDS, pixel_y = spawned.pixel_y - 32) + animate(spawned, alpha = 0, time = duration) + addtimer(CALLBACK(src, PROC_REF(delete_particle), spawned), duration) diff --git a/tgstation.dme b/tgstation.dme index a6c4a365c058..d3f1a5831b25 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6341,6 +6341,22 @@ #include "monkestation\code\modules\surgery\organs\internal\lungs.dm" #include "monkestation\code\modules\surgery\organs\internal\stomach.dm" #include "monkestation\code\modules\surgery\organs\internal\tongue.dm" +#include "monkestation\code\modules\trading\box_rolling.dm" +#include "monkestation\code\modules\trading\lootbox_buying.dm" +#include "monkestation\code\modules\trading\lootbox_odds.dm" +#include "monkestation\code\modules\trading\save_unusual_preference.dm" +#include "monkestation\code\modules\trading\unusual_effects\_unusual_component.dm" +#include "monkestation\code\modules\trading\unusual_effects\animation_housing\__spawning_component.dm" +#include "monkestation\code\modules\trading\unusual_effects\animation_housing\_footprint.dm" +#include "monkestation\code\modules\trading\unusual_effects\animation_housing\confetti.dm" +#include "monkestation\code\modules\trading\unusual_effects\animation_housing\fire.dm" +#include "monkestation\code\modules\trading\unusual_effects\animation_housing\galaxies.dm" +#include "monkestation\code\modules\trading\unusual_effects\animation_housing\holy_steps.dm" +#include "monkestation\code\modules\trading\unusual_effects\animation_housing\music.dm" +#include "monkestation\code\modules\trading\unusual_effects\animation_housing\shooting_stars.dm" +#include "monkestation\code\modules\trading\unusual_effects\animation_housing\skull_rain.dm" +#include "monkestation\code\modules\trading\unusual_effects\animation_housing\snow.dm" +#include "monkestation\code\modules\trading\unusual_effects\animation_housing\weh.dm" #include "monkestation\code\modules\twitch_bits\admin_command.dm" #include "monkestation\code\modules\twitch_bits\twitch_system.dm" #include "monkestation\code\modules\twitch_bits\events\amongus.dm" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/LoadoutPage.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/LoadoutPage.tsx index 8bc0457c6356..4ef557d1f5ca 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/LoadoutPage.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/LoadoutPage.tsx @@ -44,7 +44,13 @@ const CharacterControls = (props: { export const LoadoutManager = (props, context) => { const { act, data } = useBackend(context); - const { selected_loadout, loadout_tabs, user_is_donator, total_coins } = data; + const { + selected_loadout, + loadout_tabs, + user_is_donator, + total_coins, + selected_unusuals, + } = data; const [multiNameInputOpen, setMultiNameInputOpen] = useLocalState( context, 'multiNameInputOpen', @@ -228,7 +234,13 @@ export const LoadoutManager = (props, context) => { )} { onClick={() => act('select_item', { path: item.path, + unusual_spawning_requirements: + item.unusual_spawning_requirements, + unusual_placement: item.unusual_placement, deselect: selected_loadout.includes( item.path ), diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts index b648b8054644..c8004453dc09 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts @@ -92,6 +92,7 @@ export type QuirkInfo = { export type LoadoutInfo = { user_is_donator: BooleanLike; selected_loadout: string[]; + selected_unusuals: string[]; }; export enum RandomSetting { @@ -178,6 +179,7 @@ export type PreferencesMenuData = { user_is_donator: BooleanLike; selected_loadout: string[]; + selected_unusuals: string[]; total_coins: number; loadout_tabs: LoadoutData[]; window: Window; @@ -191,12 +193,14 @@ type LoadoutData = { type LoadoutItem = { name: string; path: string; + unusual_placement: number; is_greyscale: boolean; is_renamable: boolean; is_job_restricted: boolean; is_donator_only: boolean; is_ckey_whitelisted: boolean; tooltip_text: string; + unusual_spawning_requirements: boolean; }; export type ServerData = { jobs: {