From c6b0370b6a86c2e72b6a983df5cc3252d48ba9f7 Mon Sep 17 00:00:00 2001 From: PsiOmega Date: Sat, 7 Mar 2015 17:55:18 +0100 Subject: [PATCH 1/6] Mirror and ERT appearance improvement. NanoUI-based appearance changer, with flags to set exactly which parts of the appearance which can be changed. --- baystation12.dme | 2 + code/game/objects/structures/mirror.dm | 54 +- code/game/response_team.dm | 576 ++++++++---------- .../mob/living/carbon/human/appearance.dm | 194 ++++++ code/modules/nano/modules/human_appearance.dm | 135 ++++ code/modules/nano/nanointeraction.dm | 4 +- code/setup.dm | 17 +- nano/templates/appearance_changer.tmpl | 75 +++ 8 files changed, 676 insertions(+), 381 deletions(-) create mode 100644 code/modules/mob/living/carbon/human/appearance.dm create mode 100644 code/modules/nano/modules/human_appearance.dm create mode 100644 nano/templates/appearance_changer.tmpl diff --git a/baystation12.dme b/baystation12.dme index a8ceac79f0e..cfc5d1f08ee 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -1095,6 +1095,7 @@ #include "code\modules\mob\living\carbon\brain\posibrain.dm" #include "code\modules\mob\living\carbon\brain\robot.dm" #include "code\modules\mob\living\carbon\brain\say.dm" +#include "code\modules\mob\living\carbon\human\appearance.dm" #include "code\modules\mob\living\carbon\human\death.dm" #include "code\modules\mob\living\carbon\human\emote.dm" #include "code\modules\mob\living\carbon\human\examine.dm" @@ -1256,6 +1257,7 @@ #include "code\modules\nano\nanoui.dm" #include "code\modules\nano\modules\alarm_monitor.dm" #include "code\modules\nano\modules\crew_monitor.dm" +#include "code\modules\nano\modules\human_appearance.dm" #include "code\modules\nano\modules\power_monitor.dm" #include "code\modules\nano\modules\rcon.dm" #include "code\modules\organs\blood.dm" diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 93868d720d8..0bb32f53ec8 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -7,59 +7,19 @@ density = 0 anchored = 1 var/shattered = 0 + var/list/ui_users = list() /obj/structure/mirror/attack_hand(mob/user as mob) if(shattered) return if(ishuman(user)) - var/mob/living/carbon/human/H = user - - if(H.a_intent == "hurt") - if(prob(30) || H.species.can_shred(H)) - attack_generic(user,1) - else - attack_generic(user) - return - - var/userloc = H.loc - - //see code/modules/mob/new_player/preferences.dm at approx line 545 for comments! - //this is largely copypasted from there. - - //handle facial hair (if necessary) - if(H.gender == MALE) - var/list/species_facial_hair = list() - if(H.species) - for(var/i in facial_hair_styles_list) - var/datum/sprite_accessory/facial_hair/tmp_facial = facial_hair_styles_list[i] - if(H.species.name in tmp_facial.species_allowed) - species_facial_hair += i - else - species_facial_hair = facial_hair_styles_list - - var/new_style = input(user, "Select a facial hair style", "Grooming") as null|anything in species_facial_hair - if(userloc != H.loc) return //no tele-grooming - if(new_style) - H.f_style = new_style - - //handle normal hair - var/list/species_hair = list() - if(H.species) - for(var/i in hair_styles_list) - var/datum/sprite_accessory/hair/tmp_hair = hair_styles_list[i] - if(H.species.name in tmp_hair.species_allowed) - species_hair += i - else - species_hair = hair_styles_list - - var/new_style = input(user, "Select a hair style", "Grooming") as null|anything in species_hair - if(userloc != H.loc) return //no tele-grooming - if(new_style) - H.h_style = new_style - - H.update_hair() - + var/obj/nano_module/appearance_changer/AC = ui_users[user] + if(!AC) + AC = new(src, user) + AC.name = "SalonPro Nano-Mirror(TM)" + ui_users[user] = AC + AC.ui_interact(user) /obj/structure/mirror/proc/shatter() if(shattered) return diff --git a/code/game/response_team.dm b/code/game/response_team.dm index dfaec22189c..004addb0508 100644 --- a/code/game/response_team.dm +++ b/code/game/response_team.dm @@ -1,331 +1,245 @@ -//STRIKE TEAMS -//Thanks to Kilakk for the admin-button portion of this code. - -var/list/response_team_members = list() -var/global/send_emergency_team = 0 // Used for automagic response teams - // 'admin_emergency_team' for admin-spawned response teams -var/ert_base_chance = 10 // Default base chance. Will be incremented by increment ERT chance. -var/can_call_ert - -/client/proc/response_team() - set name = "Dispatch Emergency Response Team" - set category = "Special Verbs" - set desc = "Send an emergency response team to the station" - - if(!holder) - usr << "\red Only administrators may use this command." - return - if(!ticker) - usr << "\red The game hasn't started yet!" - return - if(ticker.current_state == 1) - usr << "\red The round hasn't started yet!" - return - if(send_emergency_team) - usr << "\red Central Command has already dispatched an emergency response team!" - return - if(alert("Do you want to dispatch an Emergency Response Team?",,"Yes","No") != "Yes") - return - if(get_security_level() != "red") // Allow admins to reconsider if the alert level isn't Red - switch(alert("The station is not in red alert. Do you still want to dispatch a response team?",,"Yes","No")) - if("No") - return - if(send_emergency_team) - usr << "\red Looks like somebody beat you to it!" - return - - message_admins("[key_name_admin(usr)] is dispatching an Emergency Response Team.", 1) - log_admin("[key_name(usr)] used Dispatch Response Team.") - trigger_armed_response_team(1) - - -client/verb/JoinResponseTeam() - set category = "IC" - - if(istype(usr,/mob/dead/observer) || istype(usr,/mob/new_player)) - if(!send_emergency_team) - usr << "No emergency response team is currently being sent." - return - /* if(admin_emergency_team) - usr << "An emergency response team has already been sent." - return */ - if(jobban_isbanned(usr, "Syndicate") || jobban_isbanned(usr, "Emergency Response Team") || jobban_isbanned(usr, "Security Officer")) - usr << "You are jobbanned from the emergency reponse team!" - return - - if(response_team_members.len > 5) usr << "The emergency response team is already full!" - - - for (var/obj/effect/landmark/L in landmarks_list) if (L.name == "Commando") - L.name = null//Reserving the place. - var/new_name = input(usr, "Pick a name","Name") as null|text - if(!new_name)//Somebody changed his mind, place is available again. - L.name = "Commando" - return - var/leader_selected = isemptylist(response_team_members) - var/mob/living/carbon/human/new_commando = create_response_team(L.loc, leader_selected, new_name) - del(L) - new_commando.mind.key = usr.key - new_commando.key = usr.key - - new_commando << "\blue You are [!leader_selected?"a member":"the LEADER"] of an Emergency Response Team, a type of military division, under CentComm's service. There is a code red alert on [station_name()], you are tasked to go and fix the problem." - new_commando << "You should first gear up and discuss a plan with your team. More members may be joining, don't move out before you're ready." - if(!leader_selected) - new_commando << "As member of the Emergency Response Team, you answer only to your leader and CentComm officials." - else - new_commando << "As leader of the Emergency Response Team, you answer only to CentComm, and have authority to override the Captain where it is necessary to achieve your mission goals. It is recommended that you attempt to cooperate with the captain where possible, however." - return - - else - usr << "You need to be an observer or new player to use this." - -// returns a number of dead players in % -proc/percentage_dead() - var/total = 0 - var/deadcount = 0 - for(var/mob/living/carbon/human/H in mob_list) - if(H.client) // Monkeys and mice don't have a client, amirite? - if(H.stat == 2) deadcount++ - total++ - - if(total == 0) return 0 - else return round(100 * deadcount / total) - -// counts the number of antagonists in % -proc/percentage_antagonists() - var/total = 0 - var/antagonists = 0 - for(var/mob/living/carbon/human/H in mob_list) - if(is_special_character(H) >= 1) - antagonists++ - total++ - - if(total == 0) return 0 - else return round(100 * antagonists / total) - -// Increments the ERT chance automatically, so that the later it is in the round, -// the more likely an ERT is to be able to be called. -proc/increment_ert_chance() - while(send_emergency_team == 0) // There is no ERT at the time. - if(get_security_level() == "green") - ert_base_chance += 1 - if(get_security_level() == "blue") - ert_base_chance += 2 - if(get_security_level() == "red") - ert_base_chance += 3 - if(get_security_level() == "delta") - ert_base_chance += 10 // Need those big guns - sleep(600 * 3) // Minute * Number of Minutes - - -proc/trigger_armed_response_team(var/force = 0) - if(!can_call_ert && !force) - return - if(send_emergency_team) - return - - var/send_team_chance = ert_base_chance // Is incremented by increment_ert_chance. - send_team_chance += 2*percentage_dead() // the more people are dead, the higher the chance - send_team_chance += percentage_antagonists() // the more antagonists, the higher the chance - send_team_chance = min(send_team_chance, 100) - - if(force) send_team_chance = 100 - - // there's only a certain chance a team will be sent - if(!prob(send_team_chance)) - command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. Unfortunately, we were unable to send one at this time.", "Central Command") - can_call_ert = 0 // Only one call per round, ladies. - return - - command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. We will prepare and send one as soon as possible.", "Central Command") - - can_call_ert = 0 // Only one call per round, gentleman. - send_emergency_team = 1 - - sleep(600 * 5) - send_emergency_team = 0 // Can no longer join the ERT. - -/* var/area/security/nuke_storage/nukeloc = locate()//To find the nuke in the vault - var/obj/machinery/nuclearbomb/nuke = locate() in nukeloc - if(!nuke) - nuke = locate() in world - var/obj/item/weapon/paper/P = new - P.info = "Your orders, Commander, are to use all means necessary to return the station to a survivable condition.
To this end, you have been provided with the best tools we can give in the three areas of Medicine, Engineering, and Security. The nuclear authorization code is: [ nuke ? nuke.r_code : "AHH, THE NUKE IS GONE!"]. Be warned, if you detonate this without good reason, we will hold you to account for damages. Memorise this code, and then burn this message." - P.name = "Emergency Nuclear Code, and ERT Orders" - for (var/obj/effect/landmark/A in world) - if (A.name == "nukecode") - P.loc = A.loc - del(A) - continue -*/ - -/client/proc/create_response_team(obj/spawn_location, leader_selected = 0, commando_name) - - //usr << "\red ERT has been temporarily disabled. Talk to a coder." - //return - - var/mob/living/carbon/human/M = new(null) - response_team_members |= M - - //todo: god damn this. - //make it a panel, like in character creation - var/new_facial = input("Please select facial hair color.", "Character Generation") as color - if(new_facial) - M.r_facial = hex2num(copytext(new_facial, 2, 4)) - M.g_facial = hex2num(copytext(new_facial, 4, 6)) - M.b_facial = hex2num(copytext(new_facial, 6, 8)) - - var/new_hair = input("Please select hair color.", "Character Generation") as color - if(new_facial) - M.r_hair = hex2num(copytext(new_hair, 2, 4)) - M.g_hair = hex2num(copytext(new_hair, 4, 6)) - M.b_hair = hex2num(copytext(new_hair, 6, 8)) - - var/new_eyes = input("Please select eye color.", "Character Generation") as color - if(new_eyes) - M.r_eyes = hex2num(copytext(new_eyes, 2, 4)) - M.g_eyes = hex2num(copytext(new_eyes, 4, 6)) - M.b_eyes = hex2num(copytext(new_eyes, 6, 8)) - - var/new_tone = input("Please select skin tone level: 1-220 (1=albino, 35=caucasian, 150=black, 220='very' black)", "Character Generation") as text - - if (!new_tone) - new_tone = 35 - M.s_tone = max(min(round(text2num(new_tone)), 220), 1) - M.s_tone = -M.s_tone + 35 - - // hair - var/list/all_hairs = typesof(/datum/sprite_accessory/hair) - /datum/sprite_accessory/hair - var/list/hairs = list() - - // loop through potential hairs - for(var/x in all_hairs) - var/datum/sprite_accessory/hair/H = new x // create new hair datum based on type x - hairs.Add(H.name) // add hair name to hairs - del(H) // delete the hair after it's all done - -// var/new_style = input("Please select hair style", "Character Generation") as null|anything in hairs -//hair - var/new_hstyle = input(usr, "Select a hair style", "Grooming") as null|anything in hair_styles_list - if(new_hstyle) - M.h_style = new_hstyle - - // facial hair - var/new_fstyle = input(usr, "Select a facial hair style", "Grooming") as null|anything in facial_hair_styles_list - if(new_fstyle) - M.f_style = new_fstyle - - // if new style selected (not cancel) -/* if (new_style) - M.h_style = new_style - - for(var/x in all_hairs) // loop through all_hairs again. Might be slightly CPU expensive, but not significantly. - var/datum/sprite_accessory/hair/H = new x // create new hair datum - if(H.name == new_style) - M.h_style = H // assign the hair_style variable a new hair datum - break - else - del(H) // if hair H not used, delete. BYOND can garbage collect, but better safe than sorry - - // facial hair - var/list/all_fhairs = typesof(/datum/sprite_accessory/facial_hair) - /datum/sprite_accessory/facial_hair - var/list/fhairs = list() - - for(var/x in all_fhairs) - var/datum/sprite_accessory/facial_hair/H = new x - fhairs.Add(H.name) - del(H) - - new_style = input("Please select facial style", "Character Generation") as null|anything in fhairs - - if(new_style) - M.f_style = new_style - for(var/x in all_fhairs) - var/datum/sprite_accessory/facial_hair/H = new x - if(H.name == new_style) - M.f_style = H - break - else - del(H) -*/ - var/new_gender = alert(usr, "Please select gender.", "Character Generation", "Male", "Female") - if (new_gender) - if(new_gender == "Male") - M.gender = MALE - else - M.gender = FEMALE - //M.rebuild_appearance() - M.update_hair() - M.update_body() - M.check_dna(M) - - M.real_name = commando_name - M.name = commando_name - M.age = !leader_selected ? rand(23,35) : rand(35,45) - - M.dna.ready_dna(M)//Creates DNA. - - //Creates mind stuff. - M.mind = new - M.mind.current = M - M.mind.original = M - M.mind.assigned_role = "MODE" - M.mind.special_role = "Response Team" - if(!(M.mind in ticker.minds)) - ticker.minds += M.mind//Adds them to regular mind list. - M.loc = spawn_location - M.equip_strike_team(leader_selected) - return M - -/mob/living/carbon/human/proc/equip_strike_team(leader_selected = 0) - - //Special radio setup - equip_to_slot_or_del(new /obj/item/device/radio/headset/ert(src), slot_l_ear) - - //Replaced with new ERT uniform - equip_to_slot_or_del(new /obj/item/clothing/under/ert(src), slot_w_uniform) - equip_to_slot_or_del(new /obj/item/clothing/shoes/swat(src), slot_shoes) - equip_to_slot_or_del(new /obj/item/clothing/gloves/swat(src), slot_gloves) - equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses(src), slot_glasses) -/* - - //Old ERT Uniform - //Basic Uniform - equip_to_slot_or_del(new /obj/item/clothing/under/syndicate/tacticool(src), slot_w_uniform) - equip_to_slot_or_del(new /obj/item/device/flashlight(src), slot_l_store) - equip_to_slot_or_del(new /obj/item/weapon/clipboard(src), slot_r_store) - equip_to_slot_or_del(new /obj/item/weapon/gun/energy/gun(src), slot_belt) - equip_to_slot_or_del(new /obj/item/clothing/mask/gas/swat(src), slot_wear_mask) - - //Glasses - equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses/sechud(src), slot_glasses) - - //Shoes & gloves - equip_to_slot_or_del(new /obj/item/clothing/shoes/swat(src), slot_shoes) - equip_to_slot_or_del(new /obj/item/clothing/gloves/swat(src), slot_gloves) - - //Removed -// equip_to_slot_or_del(new /obj/item/clothing/suit/armor/swat(src), slot_wear_suit) -// equip_to_slot_or_del(new /obj/item/clothing/head/helmet/space/deathsquad(src), slot_head) - - //Backpack - equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/security(src), slot_back) - equip_to_slot_or_del(new /obj/item/weapon/storage/box/engineer(src), slot_in_backpack) - equip_to_slot_or_del(new /obj/item/weapon/storage/firstaid/regular(src), slot_in_backpack) -*/ - var/obj/item/weapon/card/id/W = new(src) - W.assignment = "Emergency Response Team[leader_selected ? " Leader" : ""]" - W.registered_name = real_name - W.name = "[real_name]'s ID Card ([W.assignment])" - W.icon_state = "centcom" - W.access = get_all_accesses() - W.access += get_all_centcom_access() - equip_to_slot_or_del(W, slot_wear_id) - - return 1 - -//debug verb (That is horribly coded, LEAVE THIS OFF UNLESS PRIVATELY TESTING. Seriously. -/*client/verb/ResponseTeam() - set category = "Admin" - if(!send_emergency_team) - send_emergency_team = 1*/ +//STRIKE TEAMS +//Thanks to Kilakk for the admin-button portion of this code. + +var/list/response_team_members = list() +var/global/send_emergency_team = 0 // Used for automagic response teams + // 'admin_emergency_team' for admin-spawned response teams +var/ert_base_chance = 10 // Default base chance. Will be incremented by increment ERT chance. +var/can_call_ert + +/client/proc/response_team() + set name = "Dispatch Emergency Response Team" + set category = "Special Verbs" + set desc = "Send an emergency response team to the station" + + if(!holder) + usr << "\red Only administrators may use this command." + return + if(!ticker) + usr << "\red The game hasn't started yet!" + return + if(ticker.current_state == 1) + usr << "\red The round hasn't started yet!" + return + if(send_emergency_team) + usr << "\red Central Command has already dispatched an emergency response team!" + return + if(alert("Do you want to dispatch an Emergency Response Team?",,"Yes","No") != "Yes") + return + if(get_security_level() != "red") // Allow admins to reconsider if the alert level isn't Red + switch(alert("The station is not in red alert. Do you still want to dispatch a response team?",,"Yes","No")) + if("No") + return + if(send_emergency_team) + usr << "\red Looks like somebody beat you to it!" + return + + message_admins("[key_name_admin(usr)] is dispatching an Emergency Response Team.", 1) + log_admin("[key_name(usr)] used Dispatch Response Team.") + trigger_armed_response_team(1) + + +client/verb/JoinResponseTeam() + set category = "IC" + + if(istype(usr,/mob/dead/observer) || istype(usr,/mob/new_player)) + if(!send_emergency_team) + usr << "No emergency response team is currently being sent." + return + /* if(admin_emergency_team) + usr << "An emergency response team has already been sent." + return */ + if(jobban_isbanned(usr, "Syndicate") || jobban_isbanned(usr, "Emergency Response Team") || jobban_isbanned(usr, "Security Officer")) + usr << "You are jobbanned from the emergency reponse team!" + return + + if(response_team_members.len > 5) usr << "The emergency response team is already full!" + + + for (var/obj/effect/landmark/L in landmarks_list) if (L.name == "Commando") + L.name = null//Reserving the place. + var/new_name = input(usr, "Pick a name","Name") as null|text + if(!new_name)//Somebody changed his mind, place is available again. + L.name = "Commando" + return + var/leader_selected = isemptylist(response_team_members) + var/mob/living/carbon/human/new_commando = create_response_team(L.loc, leader_selected, new_name) + del(L) + //Creates mind stuff. + new_commando.mind.key = usr.key + new_commando.key = usr.key + + new_commando << "\blue You are [!leader_selected?"a member":"the LEADER"] of an Emergency Response Team, a type of military division, under CentComm's service. There is a code red alert on [station_name()], you are tasked to go and fix the problem." + new_commando << "You should first gear up and discuss a plan with your team. More members may be joining, don't move out before you're ready." + if(!leader_selected) + new_commando << "As member of the Emergency Response Team, you answer only to your leader and CentComm officials." + else + new_commando << "As leader of the Emergency Response Team, you answer only to CentComm, and have authority to override the Captain where it is necessary to achieve your mission goals. It is recommended that you attempt to cooperate with the captain where possible, however." + + // By setting an explicit location the mob cannot wander off and decide change appearance elsewhere + new_commando.change_appearance(APPEARANCE_ALL, new_commando.loc, new_commando) + + return + + else + usr << "You need to be an observer or new player to use this." + +// returns a number of dead players in % +proc/percentage_dead() + var/total = 0 + var/deadcount = 0 + for(var/mob/living/carbon/human/H in mob_list) + if(H.client) // Monkeys and mice don't have a client, amirite? + if(H.stat == 2) deadcount++ + total++ + + if(total == 0) return 0 + else return round(100 * deadcount / total) + +// counts the number of antagonists in % +proc/percentage_antagonists() + var/total = 0 + var/antagonists = 0 + for(var/mob/living/carbon/human/H in mob_list) + if(is_special_character(H) >= 1) + antagonists++ + total++ + + if(total == 0) return 0 + else return round(100 * antagonists / total) + +// Increments the ERT chance automatically, so that the later it is in the round, +// the more likely an ERT is to be able to be called. +proc/increment_ert_chance() + while(send_emergency_team == 0) // There is no ERT at the time. + if(get_security_level() == "green") + ert_base_chance += 1 + if(get_security_level() == "blue") + ert_base_chance += 2 + if(get_security_level() == "red") + ert_base_chance += 3 + if(get_security_level() == "delta") + ert_base_chance += 10 // Need those big guns + sleep(600 * 3) // Minute * Number of Minutes + + +proc/trigger_armed_response_team(var/force = 0) + if(!can_call_ert && !force) + return + if(send_emergency_team) + return + + var/send_team_chance = ert_base_chance // Is incremented by increment_ert_chance. + send_team_chance += 2*percentage_dead() // the more people are dead, the higher the chance + send_team_chance += percentage_antagonists() // the more antagonists, the higher the chance + send_team_chance = min(send_team_chance, 100) + + if(force) send_team_chance = 100 + + // there's only a certain chance a team will be sent + if(!prob(send_team_chance)) + command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. Unfortunately, we were unable to send one at this time.", "Central Command") + can_call_ert = 0 // Only one call per round, ladies. + return + + command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. We will prepare and send one as soon as possible.", "Central Command") + + can_call_ert = 0 // Only one call per round, gentleman. + send_emergency_team = 1 + + sleep(600 * 5) + send_emergency_team = 0 // Can no longer join the ERT. + +/* var/area/security/nuke_storage/nukeloc = locate()//To find the nuke in the vault + var/obj/machinery/nuclearbomb/nuke = locate() in nukeloc + if(!nuke) + nuke = locate() in world + var/obj/item/weapon/paper/P = new + P.info = "Your orders, Commander, are to use all means necessary to return the station to a survivable condition.
To this end, you have been provided with the best tools we can give in the three areas of Medicine, Engineering, and Security. The nuclear authorization code is: [ nuke ? nuke.r_code : "AHH, THE NUKE IS GONE!"]. Be warned, if you detonate this without good reason, we will hold you to account for damages. Memorise this code, and then burn this message." + P.name = "Emergency Nuclear Code, and ERT Orders" + for (var/obj/effect/landmark/A in world) + if (A.name == "nukecode") + P.loc = A.loc + del(A) + continue +*/ + +/client/proc/create_response_team(obj/spawn_location, leader_selected = 0, commando_name) + + //usr << "\red ERT has been temporarily disabled. Talk to a coder." + //return + + var/mob/living/carbon/human/M = new(null) + response_team_members |= M + + M.real_name = commando_name + M.name = commando_name + M.age = !leader_selected ? rand(23,35) : rand(35,45) + + M.check_dna(M) + M.dna.ready_dna(M)//Creates DNA. + + M.mind = new + M.mind.current = M + M.mind.original = M + M.mind.assigned_role = "MODE" + M.mind.special_role = "Response Team" + if(!(M.mind in ticker.minds)) + ticker.minds += M.mind//Adds them to regular mind list. + M.loc = spawn_location + M.equip_strike_team(leader_selected) + + return M + +/mob/living/carbon/human/proc/equip_strike_team(leader_selected = 0) + + //Special radio setup + equip_to_slot_or_del(new /obj/item/device/radio/headset/ert(src), slot_l_ear) + + //Replaced with new ERT uniform + equip_to_slot_or_del(new /obj/item/clothing/under/ert(src), slot_w_uniform) + equip_to_slot_or_del(new /obj/item/clothing/shoes/swat(src), slot_shoes) + equip_to_slot_or_del(new /obj/item/clothing/gloves/swat(src), slot_gloves) + equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses(src), slot_glasses) +/* + + //Old ERT Uniform + //Basic Uniform + equip_to_slot_or_del(new /obj/item/clothing/under/syndicate/tacticool(src), slot_w_uniform) + equip_to_slot_or_del(new /obj/item/device/flashlight(src), slot_l_store) + equip_to_slot_or_del(new /obj/item/weapon/clipboard(src), slot_r_store) + equip_to_slot_or_del(new /obj/item/weapon/gun/energy/gun(src), slot_belt) + equip_to_slot_or_del(new /obj/item/clothing/mask/gas/swat(src), slot_wear_mask) + + //Glasses + equip_to_slot_or_del(new /obj/item/clothing/glasses/sunglasses/sechud(src), slot_glasses) + + //Shoes & gloves + equip_to_slot_or_del(new /obj/item/clothing/shoes/swat(src), slot_shoes) + equip_to_slot_or_del(new /obj/item/clothing/gloves/swat(src), slot_gloves) + + //Removed +// equip_to_slot_or_del(new /obj/item/clothing/suit/armor/swat(src), slot_wear_suit) +// equip_to_slot_or_del(new /obj/item/clothing/head/helmet/space/deathsquad(src), slot_head) + + //Backpack + equip_to_slot_or_del(new /obj/item/weapon/storage/backpack/security(src), slot_back) + equip_to_slot_or_del(new /obj/item/weapon/storage/box/engineer(src), slot_in_backpack) + equip_to_slot_or_del(new /obj/item/weapon/storage/firstaid/regular(src), slot_in_backpack) +*/ + var/obj/item/weapon/card/id/W = new(src) + W.assignment = "Emergency Response Team[leader_selected ? " Leader" : ""]" + W.registered_name = real_name + W.name = "[real_name]'s ID Card ([W.assignment])" + W.icon_state = "centcom" + W.access = get_all_accesses() + W.access += get_all_centcom_access() + equip_to_slot_or_del(W, slot_wear_id) + + return 1 + +//debug verb (That is horribly coded, LEAVE THIS OFF UNLESS PRIVATELY TESTING. Seriously. +/*client/verb/ResponseTeam() + set category = "Admin" + if(!send_emergency_team) + send_emergency_team = 1*/ diff --git a/code/modules/mob/living/carbon/human/appearance.dm b/code/modules/mob/living/carbon/human/appearance.dm new file mode 100644 index 00000000000..2c7e9bc5a11 --- /dev/null +++ b/code/modules/mob/living/carbon/human/appearance.dm @@ -0,0 +1,194 @@ +/mob/living/carbon/human + var/list/valid_species = list() + var/list/valid_hairstyles = list() + var/list/valid_facial_hairstyles = list() + +/mob/living/carbon/human/New() + ..() + +/mob/living/carbon/human/proc/change_appearance(var/flags = APPEARANCE_ALL_HAIR, var/location = src, var/mob/user = src) + var/obj/nano_module/appearance_changer/AC = new(location, src) + AC.flags = flags + + generate_valid_species() + generate_valid_hairstyles() + generate_valid_facial_hairstyles() + + AC.ui_interact(user) + +/mob/living/carbon/human/proc/change_species(var/new_species) + if(!new_species) + return + + if(species == new_species) + return + + if(!(new_species in valid_species)) + return + + set_species(new_species) + reset_hair() + return 1 + +/mob/living/carbon/human/proc/change_gender(var/gender) + if(src.gender == gender) + return + + src.gender = gender + reset_hair() + update_body() + update_dna() + return 1 + +/mob/living/carbon/human/proc/change_hair(var/hair_style) + if(!hair_style) + return + + if(h_style == hair_style) + return + + if(!(hair_style in valid_hairstyles)) + return + + h_style = hair_style + + update_hair() + return 1 + +/mob/living/carbon/human/proc/change_facial_hair(var/facial_hair_style) + if(!facial_hair_style) + return + + if(f_style == facial_hair_style) + return + + if(!(facial_hair_style in valid_facial_hairstyles)) + return + + f_style = facial_hair_style + + update_hair() + return 1 + +/mob/living/carbon/human/proc/reset_hair() + generate_valid_hairstyles() + generate_valid_facial_hairstyles() + + if(valid_hairstyles.len) + h_style = pick(valid_hairstyles) + else + //this shouldn't happen + h_style = "Bald" + + if(valid_facial_hairstyles.len) + f_style = pick(valid_facial_hairstyles) + else + //this shouldn't happen + f_style = "Shaved" + + update_hair() + +/mob/living/carbon/human/proc/change_eye_color(var/red, var/green, var/blue) + if(red == r_eyes && green == g_eyes && blue == b_eyes) + return + + r_eyes = red + g_eyes = green + b_eyes = blue + + update_body() + return 1 + +/mob/living/carbon/human/proc/change_hair_color(var/red, var/green, var/blue) + if(red == r_eyes && green == g_eyes && blue == b_eyes) + return + + r_hair = red + g_hair = green + b_hair = blue + + update_hair() + return 1 + +/mob/living/carbon/human/proc/change_facial_hair_color(var/red, var/green, var/blue) + if(red == r_facial && green == g_facial && blue == b_facial) + return + + r_facial = red + g_facial = green + b_facial = blue + + update_hair() + return 1 + +/mob/living/carbon/human/proc/change_skin_color(var/red, var/green, var/blue) + if(red == r_skin && green == g_skin && blue == b_skin || !(species.flags & HAS_SKIN_COLOR)) + return + + r_skin = red + g_skin = green + b_skin = blue + + update_body() + return 1 + +/mob/living/carbon/human/proc/change_skin_tone(var/tone) + if(s_tone == tone || !(species.flags & HAS_SKIN_TONE)) + return + + s_tone = tone + + update_body() + return 1 + +/mob/living/carbon/human/proc/update_dna() + check_dna() + dna.ready_dna(src) + +/mob/living/carbon/human/proc/generate_valid_species() + valid_species.Cut() + for(var/current_species_name in all_species) + var/datum/species/current_species = all_species[current_species_name] + + if(config.usealienwhitelist && !check_rights(R_ADMIN, 0)) //If we're using the whitelist, make sure to check it! + if(!(current_species.flags & CAN_JOIN)) + continue + else if((current_species.flags & IS_WHITELISTED) && !is_alien_whitelisted(src, current_species_name)) + continue + + valid_species += current_species_name + +/mob/living/carbon/human/proc/generate_valid_hairstyles() + valid_hairstyles.Cut() + for(var/hairstyle in hair_styles_list) + var/datum/sprite_accessory/S = hair_styles_list[hairstyle] + + if(gender == MALE && S.gender == FEMALE) + continue + if(gender == FEMALE && S.gender == MALE) + continue + if(!(species.name in S.species_allowed)) + continue + valid_hairstyles += hairstyle + + return valid_hairstyles + +/mob/living/carbon/human/proc/generate_valid_facial_hairstyles() + valid_facial_hairstyles.Cut() + for(var/facialhairstyle in facial_hair_styles_list) + var/datum/sprite_accessory/S = facial_hair_styles_list[facialhairstyle] + + if(gender == MALE && S.gender == FEMALE) + continue + if(gender == FEMALE && S.gender == MALE) + continue + if(!(species.name in S.species_allowed)) + continue + + valid_facial_hairstyles += facialhairstyle + + return valid_facial_hairstyles + +/proc/q() + var/mob/living/carbon/human/H = usr + H.change_appearance(APPEARANCE_ALL) diff --git a/code/modules/nano/modules/human_appearance.dm b/code/modules/nano/modules/human_appearance.dm new file mode 100644 index 00000000000..77757d256cf --- /dev/null +++ b/code/modules/nano/modules/human_appearance.dm @@ -0,0 +1,135 @@ +/obj/nano_module/appearance_changer + name = "Appearance Editor" + flags = APPEARANCE_ALL_HAIR + var/mob/living/carbon/human/owner + +/obj/nano_module/appearance_changer/New(var/location, var/mob/living/carbon/human/H) + ..() + loc = location + owner = H + +/obj/nano_module/appearance_changer/Topic(ref, href_list) + if(..()) + return 1 + + if(href_list["race"]) + if(can_change(APPEARANCE_RACE)) + return owner.change_species(href_list["race"]) + if(href_list["gender"]) + if(can_change(APPEARANCE_GENDER)) + return owner.change_gender(href_list["gender"]) + if(href_list["skin_tone"]) + if(can_change_skin_tone()) + var/new_s_tone = input(usr, "Choose your character's skin-tone:\n(Light 1 - 220 Dark)", "Skin Tone", owner.s_tone) as num|null + if(isnum(new_s_tone) && CanUseTopic(usr) == STATUS_INTERACTIVE) + new_s_tone = 35 - max(min( round(new_s_tone), 220),1) + return owner.change_skin_tone(new_s_tone) + if(href_list["skin_color"]) + if(can_change_skin_color()) + var/new_skin = input(usr, "Choose your character's skin colour: ", "Skin Color", rgb(owner.r_skin, owner.g_skin, owner.b_skin)) as color|null + if(new_skin && can_still_topic()) + var/r_skin = hex2num(copytext(new_skin, 2, 4)) + var/g_skin = hex2num(copytext(new_skin, 4, 6)) + var/b_skin = hex2num(copytext(new_skin, 6, 8)) + if(owner.change_skin_color(r_skin, g_skin, b_skin)) + update_dna() + return 1 + if(href_list["hair"]) + if(can_change(APPEARANCE_HAIR)) + if(owner.change_hair(href_list["hair"])) + update_dna() + return 1 + if(href_list["hair_color"]) + if(can_change(APPEARANCE_HAIR_COLOR)) + var/new_hair = input("Please select hair color.", "Hair Color", rgb(owner.r_hair, owner.g_hair, owner.b_hair)) as color|null + if(new_hair && can_still_topic()) + var/r_hair = hex2num(copytext(new_hair, 2, 4)) + var/g_hair = hex2num(copytext(new_hair, 4, 6)) + var/b_hair = hex2num(copytext(new_hair, 6, 8)) + if(owner.change_hair_color(r_hair, g_hair, b_hair)) + update_dna() + return 1 + if(href_list["facial_hair"]) + if(can_change(APPEARANCE_FACIAL_HAIR)) + if(owner.change_facial_hair(href_list["facial_hair"])) + update_dna() + return 1 + if(href_list["facial_hair_color"]) + if(can_change(APPEARANCE_FACIAL_HAIR_COLOR)) + var/new_facial = input("Please select facial hair color.", "Facial Hair Color", rgb(owner.r_facial, owner.g_facial, owner.b_facial)) as color|null + if(new_facial && can_still_topic()) + var/r_facial = hex2num(copytext(new_facial, 2, 4)) + var/g_facial = hex2num(copytext(new_facial, 4, 6)) + var/b_facial = hex2num(copytext(new_facial, 6, 8)) + if(owner.change_facial_hair_color(r_facial, g_facial, b_facial)) + update_dna() + return 1 + if(href_list["eye_color"]) + if(can_change(APPEARANCE_EYE_COLOR)) + var/new_eyes = input("Please select eye color.", "Eye Color", rgb(owner.r_eyes, owner.g_eyes, owner.b_eyes)) as color|null + if(new_eyes && can_still_topic()) + var/r_eyes = hex2num(copytext(new_eyes, 2, 4)) + var/g_eyes = hex2num(copytext(new_eyes, 4, 6)) + var/b_eyes = hex2num(copytext(new_eyes, 6, 8)) + if(owner.change_eye_color(r_eyes, g_eyes, b_eyes)) + update_dna() + return 1 + + return 0 + +/obj/nano_module/appearance_changer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + var/data[0] + + data["specimen"] = owner.species.name + data["gender"] = owner.gender + data["change_race"] = can_change(APPEARANCE_RACE) + if(data["change_race"]) + var/species[0] + for(var/specimen in owner.valid_species) + species[++species.len] = list("specimen" = specimen) + data["species"] = species + + data["change_gender"] = can_change(APPEARANCE_GENDER) + data["change_skin_tone"] = can_change_skin_tone() + data["change_skin_color"] = can_change_skin_color() + data["change_eye_color"] = can_change(APPEARANCE_EYE_COLOR) + data["change_hair"] = can_change(APPEARANCE_HAIR) + if(data["change_hair"]) + var/hair_styles[0] + for(var/hair_style in owner.valid_hairstyles) + hair_styles[++hair_styles.len] = list("hairstyle" = hair_style) + data["hair_styles"] = hair_styles + data["hair_style"] = owner.h_style + + data["change_facial_hair"] = can_change(APPEARANCE_FACIAL_HAIR) + if(data["change_facial_hair"]) + var/facial_hair_styles[0] + for(var/facial_hair_style in owner.valid_facial_hairstyles) + facial_hair_styles[++facial_hair_styles.len] = list("facialhairstyle" = facial_hair_style) + data["facial_hair_styles"] = facial_hair_styles + data["facial_hair_style"] = owner.f_style + + data["change_hair_color"] = can_change(APPEARANCE_HAIR_COLOR) + data["change_facial_hair_color"] = can_change(APPEARANCE_FACIAL_HAIR_COLOR) + ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "appearance_changer.tmpl", "[src.name]", 800, 450) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/obj/nano_module/appearance_changer/proc/update_dna() + if(owner && (flags & APPEARANCE_UPDATE_DNA)) + owner.update_dna() + +/obj/nano_module/appearance_changer/proc/can_change(var/flag) + return owner && (flags & flag) + +/obj/nano_module/appearance_changer/proc/can_change_skin_tone() + return owner && (flags & APPEARANCE_SKIN) && owner.species.flags & HAS_SKIN_TONE + +/obj/nano_module/appearance_changer/proc/can_change_skin_color() + return owner && (flags & APPEARANCE_SKIN) && owner.species.flags & HAS_SKIN_COLOR + +/obj/nano_module/appearance_changer/proc/can_still_topic() + return CanUseTopic(usr, list(), default_state) == STATUS_INTERACTIVE diff --git a/code/modules/nano/nanointeraction.dm b/code/modules/nano/nanointeraction.dm index 645b5261bf8..d929cfe2b58 100644 --- a/code/modules/nano/nanointeraction.dm +++ b/code/modules/nano/nanointeraction.dm @@ -1,11 +1,11 @@ -/atom/movable/proc/nano_host() +/atom/proc/nano_host() return src /obj/nano_module/nano_host() return loc -/atom/movable/proc/CanUseTopic(var/mob/user, href_list, var/datum/topic_state/custom_state) +/atom/proc/CanUseTopic(var/mob/user, href_list, var/datum/topic_state/custom_state) return user.can_use_topic(nano_host(), custom_state) diff --git a/code/setup.dm b/code/setup.dm index e500df44535..e7bd5849d01 100644 --- a/code/setup.dm +++ b/code/setup.dm @@ -767,8 +767,23 @@ var/list/be_special_flags = list( #define STATUS_UPDATE 1 // ORANGE Visability #define STATUS_DISABLED 0 // RED Visability #define STATUS_CLOSE -1 // Close the interface + //General-purpose life speed define for plants. #define HYDRO_SPEED_MULTIPLIER 1 + #define NANO_IGNORE_DISTANCE 1 -#define DEFAULT_JOB_TYPE /datum/job/assistant \ No newline at end of file +#define DEFAULT_JOB_TYPE /datum/job/assistant + +// Appearance change flags +#define APPEARANCE_UPDATE_DNA 1 +#define APPEARANCE_RACE 2|APPEARANCE_UPDATE_DNA +#define APPEARANCE_GENDER 4|APPEARANCE_UPDATE_DNA +#define APPEARANCE_SKIN 8 +#define APPEARANCE_HAIR 16 +#define APPEARANCE_HAIR_COLOR 32 +#define APPEARANCE_FACIAL_HAIR 64 +#define APPEARANCE_FACIAL_HAIR_COLOR 128 +#define APPEARANCE_EYE_COLOR 256 +#define APPEARANCE_ALL_HAIR APPEARANCE_HAIR|APPEARANCE_HAIR_COLOR|APPEARANCE_FACIAL_HAIR|APPEARANCE_FACIAL_HAIR_COLOR +#define APPEARANCE_ALL 511 diff --git a/nano/templates/appearance_changer.tmpl b/nano/templates/appearance_changer.tmpl new file mode 100644 index 00000000000..bb283f245ba --- /dev/null +++ b/nano/templates/appearance_changer.tmpl @@ -0,0 +1,75 @@ +{{if data.change_race}} +
+
+ Species: +
+
+ {{for data.species}} + {{:helper.link(value.specimen, null, { 'race' : value.specimen}, null, data.specimen == value.specimen ? 'selected' : null)}} + {{/for}} +
+
+{{/if}} + +{{if data.change_gender}} +
+
+ Gender: +
+
+ {{:helper.link('Male', null, { 'gender' : 'male'}, null, data.gender == 'male' ? 'selected' : null)}} + {{:helper.link('Female', null, { 'gender' : 'female'}, null, data.gender == 'female' ? 'selected' : null)}} +
+
+{{/if}} + +{{if data.change_eye_color || data.change_skin_tone || data.change_skin_color || data.change_hair_color || data.change_facial_hair_color}} +
+
+ Colors: +
+
+ {{if data.change_eye_color}} + {{:helper.link('Change eye color', null, { 'eye_color' : 1})}} + {{/if}} + {{if data.change_skin_tone}} + {{:helper.link('Change skin tone', null, { 'skin_tone' : 1})}} + {{/if}} + {{if data.change_skin_color}} + {{:helper.link('Change skin color', null, { 'skin_color' : 1})}} + {{/if}} + {{if data.change_hair_color}} + {{:helper.link('Change hair color', null, { 'hair_color' : 1})}} + {{/if}} + {{if data.change_facial_hair_color}} + {{:helper.link('Change facial hair color', null, { 'facial_hair_color' : 1})}} + {{/if}} +
+
+{{/if}} + +{{if data.change_hair}} +
+
+ Hair styles: +
+
+ {{for data.hair_styles}} + {{:helper.link(value.hairstyle, null, { 'hair' : value.hairstyle}, null, data.hair_style == value.hairstyle ? 'selected' : null)}} + {{/for}} +
+
+{{/if}} + +{{if data.change_facial_hair}} +
+
+ Facial hair styles: +
+
+ {{for data.facial_hair_styles}} + {{:helper.link(value.facialhairstyle, null, { 'facial_hair' : value.facialhairstyle}, null, data.facial_hair_style == value.facialhairstyle ? 'selected' : null)}} + {{/for}} +
+
+{{/if}} From 44c7b2367ab00fe6feb7e653d365fff1289050b7 Mon Sep 17 00:00:00 2001 From: PsiOmega Date: Sun, 8 Mar 2015 12:25:34 +0100 Subject: [PATCH 2/6] Admin verbs and white/blacklisting or races. Makes it possible for admins to bring out the appearance changer at will, controlled either by themselves or the mob in question. Makes it possible to define custom race white/blacklisting in the appearance changer. However, this does not override the player's own racial restrictions. --- code/game/response_team.dm | 2 +- code/modules/admin/admin_verbs.dm | 42 +++++++++++++++- .../mob/living/carbon/human/appearance.dm | 45 ++++++++--------- code/modules/nano/modules/human_appearance.dm | 48 +++++++++++++++---- 4 files changed, 99 insertions(+), 38 deletions(-) diff --git a/code/game/response_team.dm b/code/game/response_team.dm index 004addb0508..aca036c94a0 100644 --- a/code/game/response_team.dm +++ b/code/game/response_team.dm @@ -77,7 +77,7 @@ client/verb/JoinResponseTeam() new_commando << "As leader of the Emergency Response Team, you answer only to CentComm, and have authority to override the Captain where it is necessary to achieve your mission goals. It is recommended that you attempt to cooperate with the captain where possible, however." // By setting an explicit location the mob cannot wander off and decide change appearance elsewhere - new_commando.change_appearance(APPEARANCE_ALL, new_commando.loc, new_commando) + new_commando.change_appearance(APPEARANCE_ALL, new_commando.loc, new_commando, species_whitelist = list("Human", "Unathi", "Tajara")) return diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 14c70df7921..9ee6b25e7b2 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -79,7 +79,9 @@ var/list/admin_verbs_admin = list( /client/proc/toggle_antagHUD_restrictions, /client/proc/allow_character_respawn, /* Allows a ghost to respawn */ /client/proc/event_manager_panel, - /client/proc/empty_ai_core_toggle_latejoin + /client/proc/empty_ai_core_toggle_latejoin, + /client/proc/change_human_appearance_admin, /* Allows an admin to change the basic appearance of human-based mobs */ + /client/proc/change_human_appearance_self /* Allows the human-based mob itself change its basic appearance */ ) var/list/admin_verbs_ban = list( /client/proc/unban_panel, @@ -703,6 +705,9 @@ var/list/admin_verbs_mentor = list( set name = "Rename Silicon" set category = "Admin" + if(!istype(S)) + return + if(holder) var/new_name = trim_strip_input(src, "Enter new name. Leave blank or as is to cancel.", "Enter new silicon name", S.real_name) if(new_name && new_name != S.real_name) @@ -710,6 +715,41 @@ var/list/admin_verbs_mentor = list( S.SetName(new_name) feedback_add_details("admin_verb","RAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/client/proc/change_human_appearance_admin(mob/living/carbon/human/H in world) + set name = "Change Mob Appearance - Admin" + set desc = "Allows you to change the mob appearance" + set category = "Admin" + + if(!istype(H)) + return + + if(holder) + admin_log_and_message_admins("is altering the appearance of [H].") + H.change_appearance(APPEARANCE_ALL, usr, usr, check_species_whitelist = 0) + feedback_add_details("admin_verb","CHAA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/client/proc/change_human_appearance_self(mob/living/carbon/human/H in world) + set name = "Change Mob Appearance - Self" + set desc = "Allows the mob to change its appearance" + set category = "Admin" + + if(!istype(H)) + return + + if(!H.client) + usr << "Only mobs with clients can alter their own appearance." + return + + if(holder) + switch(alert("Do you wish for [H] to be allowed to select non-whitelisted races?","Alter Mob Appearance","Yes","No","Cancel")) + if("Yes") + admin_log_and_message_admins("has allowed [H] to change \his appearance, without whitelisting of races.") + H.change_appearance(APPEARANCE_ALL, H.loc, check_species_whitelist = 0) + if("No") + admin_log_and_message_admins("has allowed [H] to change \his appearance, with whitelisting of races.") + H.change_appearance(APPEARANCE_ALL, H.loc, check_species_whitelist = 1) + feedback_add_details("admin_verb","CMAS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + //---- bs12 verbs ---- diff --git a/code/modules/mob/living/carbon/human/appearance.dm b/code/modules/mob/living/carbon/human/appearance.dm index 2c7e9bc5a11..4976cba4fb1 100644 --- a/code/modules/mob/living/carbon/human/appearance.dm +++ b/code/modules/mob/living/carbon/human/appearance.dm @@ -1,19 +1,6 @@ -/mob/living/carbon/human - var/list/valid_species = list() - var/list/valid_hairstyles = list() - var/list/valid_facial_hairstyles = list() - -/mob/living/carbon/human/New() - ..() - -/mob/living/carbon/human/proc/change_appearance(var/flags = APPEARANCE_ALL_HAIR, var/location = src, var/mob/user = src) - var/obj/nano_module/appearance_changer/AC = new(location, src) +/mob/living/carbon/human/proc/change_appearance(var/flags = APPEARANCE_ALL_HAIR, var/location = src, var/mob/user = src, var/check_species_whitelist = 1, var/list/species_whitelist = list(), var/list/species_blacklist = list()) + var/obj/nano_module/appearance_changer/AC = new(location, src, check_species_whitelist, species_whitelist, species_blacklist) AC.flags = flags - - generate_valid_species() - generate_valid_hairstyles() - generate_valid_facial_hairstyles() - AC.ui_interact(user) /mob/living/carbon/human/proc/change_species(var/new_species) @@ -23,7 +10,7 @@ if(species == new_species) return - if(!(new_species in valid_species)) + if(!(new_species in all_species)) return set_species(new_species) @@ -47,7 +34,7 @@ if(h_style == hair_style) return - if(!(hair_style in valid_hairstyles)) + if(!(hair_style in hair_styles_list)) return h_style = hair_style @@ -62,7 +49,7 @@ if(f_style == facial_hair_style) return - if(!(facial_hair_style in valid_facial_hairstyles)) + if(!(facial_hair_style in facial_hair_styles_list)) return f_style = facial_hair_style @@ -71,8 +58,8 @@ return 1 /mob/living/carbon/human/proc/reset_hair() - generate_valid_hairstyles() - generate_valid_facial_hairstyles() + var/list/valid_hairstyles = generate_valid_hairstyles() + var/list/valid_facial_hairstyles = generate_valid_facial_hairstyles() if(valid_hairstyles.len) h_style = pick(valid_hairstyles) @@ -145,21 +132,27 @@ check_dna() dna.ready_dna(src) -/mob/living/carbon/human/proc/generate_valid_species() - valid_species.Cut() +/mob/living/carbon/human/proc/generate_valid_species(var/check_whitelist = 1, var/list/whitelist = list(), var/list/blacklist = list()) + var/list/valid_species = new() for(var/current_species_name in all_species) var/datum/species/current_species = all_species[current_species_name] - if(config.usealienwhitelist && !check_rights(R_ADMIN, 0)) //If we're using the whitelist, make sure to check it! + if(check_whitelist && config.usealienwhitelist && !check_rights(R_ADMIN, 0, src)) //If we're using the whitelist, make sure to check it! if(!(current_species.flags & CAN_JOIN)) continue - else if((current_species.flags & IS_WHITELISTED) && !is_alien_whitelisted(src, current_species_name)) + if(whitelist.len && !(current_species_name in whitelist)) + continue + if(blacklist.len && (current_species_name in blacklist)) + continue + if((current_species.flags & IS_WHITELISTED) && !is_alien_whitelisted(src, current_species_name)) continue valid_species += current_species_name + return valid_species + /mob/living/carbon/human/proc/generate_valid_hairstyles() - valid_hairstyles.Cut() + var/list/valid_hairstyles = new() for(var/hairstyle in hair_styles_list) var/datum/sprite_accessory/S = hair_styles_list[hairstyle] @@ -174,7 +167,7 @@ return valid_hairstyles /mob/living/carbon/human/proc/generate_valid_facial_hairstyles() - valid_facial_hairstyles.Cut() + var/list/valid_facial_hairstyles = new() for(var/facialhairstyle in facial_hair_styles_list) var/datum/sprite_accessory/S = facial_hair_styles_list[facialhairstyle] diff --git a/code/modules/nano/modules/human_appearance.dm b/code/modules/nano/modules/human_appearance.dm index 77757d256cf..a8327066e64 100644 --- a/code/modules/nano/modules/human_appearance.dm +++ b/code/modules/nano/modules/human_appearance.dm @@ -1,23 +1,37 @@ /obj/nano_module/appearance_changer name = "Appearance Editor" flags = APPEARANCE_ALL_HAIR - var/mob/living/carbon/human/owner + var/mob/living/carbon/human/owner = null + var/list/valid_species = list() + var/list/valid_hairstyles = list() + var/list/valid_facial_hairstyles = list() -/obj/nano_module/appearance_changer/New(var/location, var/mob/living/carbon/human/H) + var/check_whitelist + var/list/whitelist + var/list/blacklist + +/obj/nano_module/appearance_changer/New(var/location, var/mob/living/carbon/human/H, var/check_species_whitelist = 1, var/list/species_whitelist = list(), var/list/species_blacklist = list()) ..() loc = location owner = H + src.check_whitelist = check_species_whitelist + src.whitelist = species_whitelist + src.blacklist = species_blacklist /obj/nano_module/appearance_changer/Topic(ref, href_list) if(..()) return 1 if(href_list["race"]) - if(can_change(APPEARANCE_RACE)) - return owner.change_species(href_list["race"]) + if(can_change(APPEARANCE_RACE) && (href_list["race"] in valid_species)) + if(owner.change_species(href_list["race"])) + cut_and_generate_data() + return 1 if(href_list["gender"]) if(can_change(APPEARANCE_GENDER)) - return owner.change_gender(href_list["gender"]) + if(owner.change_gender(href_list["gender"])) + cut_and_generate_data() + return 1 if(href_list["skin_tone"]) if(can_change_skin_tone()) var/new_s_tone = input(usr, "Choose your character's skin-tone:\n(Light 1 - 220 Dark)", "Skin Tone", owner.s_tone) as num|null @@ -35,7 +49,7 @@ update_dna() return 1 if(href_list["hair"]) - if(can_change(APPEARANCE_HAIR)) + if(can_change(APPEARANCE_HAIR) && (href_list["hair"] in valid_hairstyles)) if(owner.change_hair(href_list["hair"])) update_dna() return 1 @@ -50,7 +64,7 @@ update_dna() return 1 if(href_list["facial_hair"]) - if(can_change(APPEARANCE_FACIAL_HAIR)) + if(can_change(APPEARANCE_FACIAL_HAIR) && (href_list["facial_hair"] in valid_facial_hairstyles)) if(owner.change_facial_hair(href_list["facial_hair"])) update_dna() return 1 @@ -78,6 +92,7 @@ return 0 /obj/nano_module/appearance_changer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + generate_data(check_whitelist, whitelist, blacklist) var/data[0] data["specimen"] = owner.species.name @@ -85,7 +100,7 @@ data["change_race"] = can_change(APPEARANCE_RACE) if(data["change_race"]) var/species[0] - for(var/specimen in owner.valid_species) + for(var/specimen in valid_species) species[++species.len] = list("specimen" = specimen) data["species"] = species @@ -96,7 +111,7 @@ data["change_hair"] = can_change(APPEARANCE_HAIR) if(data["change_hair"]) var/hair_styles[0] - for(var/hair_style in owner.valid_hairstyles) + for(var/hair_style in valid_hairstyles) hair_styles[++hair_styles.len] = list("hairstyle" = hair_style) data["hair_styles"] = hair_styles data["hair_style"] = owner.h_style @@ -104,7 +119,7 @@ data["change_facial_hair"] = can_change(APPEARANCE_FACIAL_HAIR) if(data["change_facial_hair"]) var/facial_hair_styles[0] - for(var/facial_hair_style in owner.valid_facial_hairstyles) + for(var/facial_hair_style in valid_facial_hairstyles) facial_hair_styles[++facial_hair_styles.len] = list("facialhairstyle" = facial_hair_style) data["facial_hair_styles"] = facial_hair_styles data["facial_hair_style"] = owner.f_style @@ -133,3 +148,16 @@ /obj/nano_module/appearance_changer/proc/can_still_topic() return CanUseTopic(usr, list(), default_state) == STATUS_INTERACTIVE + +/obj/nano_module/appearance_changer/proc/cut_and_generate_data() + // Making the assumption that the available species remain constant + valid_facial_hairstyles.Cut() + valid_facial_hairstyles.Cut() + generate_data() + +/obj/nano_module/appearance_changer/proc/generate_data() + if(!valid_species.len) + valid_species = owner.generate_valid_species(check_whitelist, whitelist, blacklist) + if(!valid_hairstyles.len || !valid_facial_hairstyles.len) + valid_hairstyles = owner.generate_valid_hairstyles() + valid_facial_hairstyles = owner.generate_valid_facial_hairstyles() \ No newline at end of file From 6749e09ead63796ccadd8de0f5868bedada1e0ef Mon Sep 17 00:00:00 2001 From: PsiOmega Date: Sun, 8 Mar 2015 15:41:54 +0100 Subject: [PATCH 3/6] Makes available ERT species a config option. --- code/controllers/configuration.dm | 6 ++++++ code/game/response_team.dm | 2 +- config/example/config.txt | 6 +++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 7f12e097d8d..a8f766a82d4 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -184,6 +184,8 @@ var/starlight = 0 // Whether space turfs have ambient light or not + var/list/ert_species = list("Human") + /datum/configuration/New() var/list/L = typesof(/datum/game_mode) - /datum/game_mode for (var/T in L) @@ -609,6 +611,10 @@ value = text2num(value) config.starlight = value >= 0 ? value : 0 + if("ert_species") + config.ert_species = text2list(value, ";") + if(!config.ert_species.len) + config.ert_species += "Human" else log_misc("Unknown setting in configuration: '[name]'") diff --git a/code/game/response_team.dm b/code/game/response_team.dm index aca036c94a0..a9dd5983741 100644 --- a/code/game/response_team.dm +++ b/code/game/response_team.dm @@ -77,7 +77,7 @@ client/verb/JoinResponseTeam() new_commando << "As leader of the Emergency Response Team, you answer only to CentComm, and have authority to override the Captain where it is necessary to achieve your mission goals. It is recommended that you attempt to cooperate with the captain where possible, however." // By setting an explicit location the mob cannot wander off and decide change appearance elsewhere - new_commando.change_appearance(APPEARANCE_ALL, new_commando.loc, new_commando, species_whitelist = list("Human", "Unathi", "Tajara")) + new_commando.change_appearance(APPEARANCE_ALL, new_commando.loc, new_commando, species_whitelist = config.ert_species) return diff --git a/config/example/config.txt b/config/example/config.txt index e0a240cf799..c708ae4a49d 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -322,5 +322,9 @@ EVENT_CUSTOM_START_MAJOR 80;100 ## Uncomment to disable respawning by default. #DISABLE_RESPAWN -## Strength of ambient star light. Set to 0 or less to turn off. +## Strength of ambient star light. Set to 0 or less to turn off. A value of 1 is unlikely to have a noticeable effect in most lightning systems. STARLIGHT 0 + +## Defines which races are allowed to join as ERT, in singular form. If unset, defaults to only human. Casing matters, separate using ; +## Example races include: Human, Tajara, Skrell, Unathi +# ERT_SPECIES Human;Skrell;Unathi From 54971c1bf50d3e597616e75001612f7d9ee1ce51 Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Mon, 9 Mar 2015 20:29:47 +1300 Subject: [PATCH 4/6] Projectile effects - Part 1 - Added base projectile effects framework - Added vector movement helper - Added temporary effect sprites --- baystation12.dme | 2 + code/__HELPERS/vector.dm | 141 ++++++++++++++++++++++++++++ code/modules/projectiles/effects.dm | 126 +++++++++++++++++++++++++ icons/effects/projectiles.dmi | Bin 0 -> 32589 bytes 4 files changed, 269 insertions(+) create mode 100644 code/__HELPERS/vector.dm create mode 100644 code/modules/projectiles/effects.dm create mode 100644 icons/effects/projectiles.dmi diff --git a/baystation12.dme b/baystation12.dme index a8ceac79f0e..1a0581e49bf 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -31,6 +31,7 @@ #include "code\__HELPERS\turfs.dm" #include "code\__HELPERS\type2type.dm" #include "code\__HELPERS\unsorted.dm" +#include "code\__HELPERS\vector.dm" #include "code\_onclick\adjacent.dm" #include "code\_onclick\ai.dm" #include "code\_onclick\click.dm" @@ -1345,6 +1346,7 @@ #include "code\modules\power\singularity\particle_accelerator\particle_emitter.dm" #include "code\modules\power\singularity\particle_accelerator\particle_power.dm" #include "code\modules\projectiles\ammunition.dm" +#include "code\modules\projectiles\effects.dm" #include "code\modules\projectiles\gun.dm" #include "code\modules\projectiles\projectile.dm" #include "code\modules\projectiles\targeting.dm" diff --git a/code/__HELPERS/vector.dm b/code/__HELPERS/vector.dm new file mode 100644 index 00000000000..970530e71df --- /dev/null +++ b/code/__HELPERS/vector.dm @@ -0,0 +1,141 @@ +/* +plot_vector is a helper datum for plotting a path in a straight line towards a target turf. +This datum converts from world space (turf.x and turf.y) to pixel space, which the datum keeps track of itself. This +should work with any size turfs (i.e. 32x32, 64x64) as it references world.icon_size (note: not actually tested with +anything other than 32x32 turfs). + +setup() + This should be called after creating a new instance of a plot_vector datum. + This does the initial setup and calculations. Since we are travelling in a straight line we only need to calculate + the vector and x/y steps once. x/y steps are capped to 1 full turf, whichever is further. If we are travelling along + the y axis each step will be +/- 1 y, and the x movement reduced based on the angle (tangent calculation). After + this every subsequent step will be incremented based on these calculations. + Inputs: + source - the turf the object is starting from + target - the target turf the object is travelling towards + xo - starting pixel_x offset, typically won't be needed, but included in case someone has a need for it later + yo - same as xo, but for the y_pixel offset + +increment() + Adds the offset to the current location - incrementing it by one step along the vector. + +return_angle() + Returns the direction (angle in degrees) the object is travelling in. + + (N) + 90° + ^ + | + (W) 180° <--+--> 0° (E) + | + v + -90° + (S) + +return_hypotenuse() + Returns the distance of travel for each step of the vector, relative to each full step of movement. 1 is a full turf + length. Currently used as a multiplier for scaling effects that should be contiguous, like laser beams. + +return_location() + Returns a vector_loc datum containing the current location data of the object (see /datum/vector_loc). This includes + the turf it currently should be at, as well as the pixel offset from the centre of that turf. Typically increment() + would be called before this if you are going to move an object based on it's vector data. +*/ + +/datum/plot_vector + var/turf/source + var/turf/target + var/angle = 0 // direction of travel in degrees + var/loc_x = 0 // in pixels from the left edge of the map + var/loc_y = 0 // in pixels from the bottom edge of the map + var/loc_z = 0 // loc z is in world space coordinates (i.e. z level) - we don't care about measuring pixels for this + var/offset_x = 0 // distance to increment each step + var/offset_y = 0 + +/datum/plot_vector/proc/setup(var/turf/S, var/turf/T, var/xo = 0, var/yo = 0) + source = S + target = T + + if(!istype(source)) + source = get_turf(source) + if(!istype(target)) + target = get_turf(target) + + if(!istype(source) || !istype(target)) + return + + // convert coordinates to pixel space (default is 32px/turf, 8160px across for a size 255 map) + loc_x = source.x * world.icon_size + xo + loc_y = source.y * world.icon_size + yo + loc_z = source.z + + // calculate initial x and y difference + var/dx = target.x - source.x + var/dy = target.y - source.y + + // if we aren't moving anywhere; quit now + if(dx == 0 && dy == 0) + return + + // calculate the angle + angle = Atan2(dx, dy) + + // and some rounding to stop the increments jumping whole turfs - because byond favours certain angles + if(angle > -135 && angle < 45) + angle = Ceiling(angle) + else + angle = Floor(angle) + + // calculate the offset per increment step + if(abs(angle) in list(0, 45, 90, 135, 180)) // check if the angle is a cardinal + if(abs(angle) in list(0, 45, 135, 180)) // if so we can skip the trigonometry and set these to absolutes as + offset_x = sign(dx) // they will always be a full step in one or more directions + if(abs(angle) in list(45, 90, 135)) + offset_y = sign(dy) + else if(abs(dy) > abs(dx)) + offset_x = Cot(abs(angle)) // otherwise set the offsets + offset_y = sign(dy) + else + offset_x = sign(dx) + offset_y = Tan(angle) + if(dx < 0) + offset_y = -offset_y + + // multiply the offset by the turf pixel size + offset_x *= world.icon_size + offset_y *= world.icon_size + +/datum/plot_vector/proc/increment() + loc_x += offset_x + loc_y += offset_y + +/datum/plot_vector/proc/return_angle() + return angle + +/datum/plot_vector/proc/return_hypotenuse() + return sqrt(((offset_x / 32) ** 2) + ((offset_y / 32) ** 2)) + +/datum/plot_vector/proc/return_location(var/datum/vector_loc/data) + if(!data) + data = new() + data.loc = locate(round(loc_x / world.icon_size), round(loc_y / world.icon_size), loc_z) + if(!data.loc) + return + data.pixel_x = loc_x - (data.loc.x * world.icon_size) + data.pixel_y = loc_y - (data.loc.y * world.icon_size) + return data + +/* +vector_loc is a helper datum for returning precise location data from plot_vector. It includes the turf the object is in +as well as the pixel offsets. + +return_turf() + Returns the turf the object should be currently located in. +*/ +/datum/vector_loc + var/turf/loc + var/pixel_x + var/pixel_y + +/datum/vector_loc/proc/return_turf() + return loc diff --git a/code/modules/projectiles/effects.dm b/code/modules/projectiles/effects.dm new file mode 100644 index 00000000000..066f4cf55d4 --- /dev/null +++ b/code/modules/projectiles/effects.dm @@ -0,0 +1,126 @@ +/obj/effect/projectile + icon = 'icons/effects/projectiles.dmi' + icon_state = "bolt" + layer = 20 + +/obj/effect/projectile/New(var/turf/location) + if(istype(location)) + loc = location + +/obj/effect/projectile/proc/set_transform(var/matrix/M) + if(istype(M)) + transform = M + +/obj/effect/projectile/proc/activate() + spawn(3) + delete() //see effect_system.dm - sets loc to null and lets GC handle removing these effects + + return + +//---------------------------- +// Laser beam +//---------------------------- +/obj/effect/projectile/laser/tracer + icon_state = "beam" + +/obj/effect/projectile/laser/muzzle + icon_state = "muzzle_laser" + +/obj/effect/projectile/laser/impact + icon_state = "impact_laser" + +//---------------------------- +// Blue laser beam +//---------------------------- +/obj/effect/projectile/laser_blue/tracer + icon_state = "beam_blue" + +/obj/effect/projectile/laser_blue/muzzle + icon_state = "muzzle_blue" + +/obj/effect/projectile/laser_blue/impact + icon_state = "impact_blue" + +//---------------------------- +// Omni laser beam +//---------------------------- +/obj/effect/projectile/laser_omni/tracer + icon_state = "beam_omni" + +/obj/effect/projectile/laser_omni/muzzle + icon_state = "muzzle_omni" + +/obj/effect/projectile/laser_omni/impact + icon_state = "impact_omni" + +//---------------------------- +// Xray laser beam +//---------------------------- +/obj/effect/projectile/xray/tracer + icon_state = "xray" + +/obj/effect/projectile/xray/muzzle + icon_state = "muzzle_xray" + +/obj/effect/projectile/xray/impact + icon_state = "impact_xray" + +//---------------------------- +// Heavy laser beam +//---------------------------- +/obj/effect/projectile/laser_heavy/tracer + icon_state = "beam_heavy" + +/obj/effect/projectile/laser_heavy/muzzle + icon_state = "muzzle_beam_heavy" + +/obj/effect/projectile/laser_heavy/impact + icon_state = "impact_beam_heavy" + +//---------------------------- +// Pulse laser beam +//---------------------------- +/obj/effect/projectile/laser_pulse/tracer + icon_state = "u_laser" + +/obj/effect/projectile/laser_pulse/muzzle + icon_state = "muzzle_u_laser" + +/obj/effect/projectile/laser_pulse/impact + icon_state = "impact_u_laser" + +//---------------------------- +// Pulse muzzle effect only +//---------------------------- +/obj/effect/projectile/pulse/muzzle + icon_state = "muzzle_pulse" + +//---------------------------- +// Emitter beam +//---------------------------- +/obj/effect/projectile/emitter/tracer + icon_state = "emitter" + +/obj/effect/projectile/emitter/muzzle + icon_state = "muzzle_emitter" + +/obj/effect/projectile/emitter/impact + icon_state = "impact_emitter" + +//---------------------------- +// Stun beam +//---------------------------- +/obj/effect/projectile/stun/tracer + icon_state = "stun" + +/obj/effect/projectile/stun/muzzle + icon_state = "muzzle_stun" + +/obj/effect/projectile/stun/impact + icon_state = "impact_stun" + +//---------------------------- +// Bullet +//---------------------------- +/obj/effect/projectile/bullet/muzzle + icon_state = "muzzle_bullet" diff --git a/icons/effects/projectiles.dmi b/icons/effects/projectiles.dmi new file mode 100644 index 0000000000000000000000000000000000000000..a2a85dd1b362a8180d58a8187cef780751dc4060 GIT binary patch literal 32589 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 A+W-In literal 0 HcmV?d00001 From 80381a87d001f234d2fec500db2142225be28100 Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Mon, 9 Mar 2015 20:36:39 +1300 Subject: [PATCH 5/6] Projectile effects - Part 2 - Converted the root projectiles object to support the new effects system at a generic level - Converted laser beams to use the new effects system and removed the relics of the old system - Added muzzle flash for ballistic ammo --- code/modules/projectiles/projectile.dm | 100 ++++++++++++++-- code/modules/projectiles/projectile/beams.dm | 108 ++++++------------ .../modules/projectiles/projectile/bullets.dm | 2 + 3 files changed, 131 insertions(+), 79 deletions(-) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 9b710409a1f..26e2bf3366d 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -51,6 +51,18 @@ var/drowsy = 0 var/agony = 0 var/embed = 0 // whether or not the projectile can embed itself in the mob + + var/hitscan = 0 // whether the projectile should be hitscan + + // effect types to be used + var/muzzle_type + var/tracer_type + var/impact_type + + var/datum/plot_vector/trajectory // used to plot the path of the projectile + var/datum/vector_loc/location // current location of the projectile in pixel space + var/matrix/effect_transform // matrix to rotate and scale projectile effects - putting it here so it doesn't + // have to be recreated multiple times //TODO: make it so this is called more reliably, instead of sometimes by bullet_act() and sometimes not /obj/item/projectile/proc/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null) @@ -63,6 +75,7 @@ //called when the projectile stops flying because it collided with something /obj/item/projectile/proc/on_impact(var/atom/A) + impact_effect(effect_transform) // generate impact effect return //Checks if the projectile is eligible for embedding. Not that it necessarily will. @@ -228,6 +241,7 @@ density = 0 invisibility = 101 + del(src) return 1 @@ -240,23 +254,86 @@ return 1 /obj/item/projectile/process() + var/first_step = 1 + + //plot the initial trajectory + setup_trajectory() + spawn while(src) if(kill_count-- < 1) on_impact(src.loc) //for any final impact behaviours - del(src) + del(src) if((!( current ) || loc == current)) current = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z) if((x == 1 || x == world.maxx || y == 1 || y == world.maxy)) del(src) return - step_towards(src, current) - sleep(1) + + trajectory.increment() // increment the current location + location = trajectory.return_location(location) // update the locally stored location data + + if(!location) + del(src) // if it's left the world... kill it + + Move(location.return_turf()) + + if(first_step) + muzzle_effect(effect_transform) + first_step = 0 + else + tracer_effect(effect_transform) + if(!bumped && !isturf(original)) if(loc == get_turf(original)) if(!(original in permutated)) Bump(original) - sleep(1) - + + if(!hitscan) + sleep(1) //add delay between movement iterations if it's not a hitscan weapon + +/obj/item/projectile/proc/setup_trajectory() + // plot the initial trajectory + trajectory = new() + trajectory.setup(starting, original, pixel_x, pixel_y) + + // generate this now since all visual effects the projectile makes can use it + effect_transform = new() + effect_transform.Scale(trajectory.return_hypotenuse(), 1) + effect_transform.Turn(-trajectory.return_angle()) //no idea why this has to be inverted, but it works + +/obj/item/projectile/proc/muzzle_effect(var/matrix/T) + if(silenced) + return + + if(ispath(muzzle_type)) + var/obj/effect/projectile/M = new muzzle_type(get_turf(src)) + + if(istype(M)) + M.set_transform(T) + M.pixel_x = location.pixel_x + M.pixel_y = location.pixel_y + M.activate() + +/obj/item/projectile/proc/tracer_effect(var/matrix/M) + if(ispath(tracer_type)) + var/obj/effect/projectile/P = new tracer_type(location.loc) + + if(istype(P)) + P.set_transform(M) + P.pixel_x = location.pixel_x + P.pixel_y = location.pixel_y + P.activate() + +/obj/item/projectile/proc/impact_effect(var/matrix/M) + if(ispath(tracer_type)) + var/obj/effect/projectile/P = new impact_type(location.loc) + + if(istype(P)) + P.set_transform(M) + P.pixel_x = location.pixel_x + P.pixel_y = location.pixel_y + P.activate() + //"Tracing" projectile /obj/item/projectile/test //Used to see if you can hit them. invisibility = 101 //Nope! Can't see me! @@ -285,12 +362,21 @@ yo = targloc.y - curloc.y xo = targloc.x - curloc.x target = targloc + + //plot the initial trajectory + setup_trajectory() + while(src) //Loop on through! if(result) return (result - 1) if((!( target ) || loc == target)) target = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z) //Finding the target turf at map edge - step_towards(src, target) + + trajectory.increment() // increment the current location + location = trajectory.return_location(location) // update the locally stored location data + + Move(location.return_turf()) + var/mob/living/M = locate() in get_turf(src) if(istype(M)) //If there is someting living... return 1 //Return 1 @@ -310,4 +396,4 @@ trace.firer = user var/output = trace.process() //Test it! del(trace) //No need for it anymore - return output //Send it back to the gun! \ No newline at end of file + return output //Send it back to the gun! diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index b5ce31b1681..cbe1ad5625b 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -1,13 +1,3 @@ -var/list/beam_master = list() -//Use: Caches beam state images and holds turfs that had these images overlaid. -//Structure: -//beam_master -// icon_states/dirs of beams -// image for that beam -// references for fired beams -// icon_states/dirs for each placed beam image -// turfs that have that icon_state/dir - /obj/item/projectile/beam name = "laser" icon_state = "laser" @@ -17,69 +7,11 @@ var/list/beam_master = list() check_armour = "laser" eyeblur = 4 var/frequency = 1 + hitscan = 1 -/obj/item/projectile/beam/process() - var/reference = "\ref[src]" //So we do not have to recalculate it a ton - var/first = 1 //So we don't make the overlay in the same tile as the firer - spawn while(src) //Move until we hit something - - if((!( current ) || loc == current)) //If we pass our target - current = locate(min(max(x + xo, 1), world.maxx), min(max(y + yo, 1), world.maxy), z) - if((x == 1 || x == world.maxx || y == 1 || y == world.maxy)) - del(src) //Delete if it passes the world edge - return - step_towards(src, current) //Move~ - - if(kill_count < 1) - del(src) - kill_count-- - - if(!bumped && !isturf(original)) - if(loc == get_turf(original)) - if(!(original in permutated)) - Bump(original) - - if(!first) //Add the overlay as we pass over tiles - var/target_dir = get_dir(src, current) //So we don't call this too much - - //If the icon has not been added yet - if( !("[icon_state][target_dir]" in beam_master) ) - var/image/I = image(icon,icon_state,10,target_dir) //Generate it. - beam_master["[icon_state][target_dir]"] = I //And cache it! - - //Finally add the overlay - src.loc.overlays += beam_master["[icon_state][target_dir]"] - - //Add the turf to a list in the beam master so they can be cleaned up easily. - if(reference in beam_master) - var/list/turf_master = beam_master[reference] - if("[icon_state][target_dir]" in turf_master) - var/list/turfs = turf_master["[icon_state][target_dir]"] - turfs += loc - else - turf_master["[icon_state][target_dir]"] = list(loc) - else - var/list/turfs = list() - turfs["[icon_state][target_dir]"] = list(loc) - beam_master[reference] = turfs - else - first = 0 - cleanup(reference) - return - -/obj/item/projectile/beam/Del() - cleanup("\ref[src]") - ..() - -/obj/item/projectile/beam/proc/cleanup(reference) //Waits .3 seconds then removes the overlay. - src = null //we're getting deleted! this will keep the code running - spawn(3) - var/list/turf_master = beam_master[reference] - for(var/laser_state in turf_master) - var/list/turfs = turf_master[laser_state] - for(var/turf/T in turfs) - T.overlays -= beam_master[laser_state] - return + muzzle_type = /obj/effect/projectile/laser/muzzle + tracer_type = /obj/effect/projectile/laser/tracer + impact_type = /obj/effect/projectile/laser/impact /obj/item/projectile/beam/practice name = "laser" @@ -95,16 +27,28 @@ var/list/beam_master = list() icon_state = "heavylaser" damage = 60 + muzzle_type = /obj/effect/projectile/laser_heavy/muzzle + tracer_type = /obj/effect/projectile/laser_heavy/tracer + impact_type = /obj/effect/projectile/laser_heavy/impact + /obj/item/projectile/beam/xray name = "xray beam" icon_state = "xray" damage = 30 + muzzle_type = /obj/effect/projectile/xray/muzzle + tracer_type = /obj/effect/projectile/xray/tracer + impact_type = /obj/effect/projectile/xray/impact + /obj/item/projectile/beam/pulse name = "pulse" icon_state = "u_laser" damage = 50 + muzzle_type = /obj/effect/projectile/laser_pulse/muzzle + tracer_type = /obj/effect/projectile/laser_pulse/tracer + impact_type = /obj/effect/projectile/laser_pulse/impact + /obj/item/projectile/beam/pulse/on_hit(var/atom/target, var/blocked = 0) if(isturf(target)) target.ex_act(2) @@ -115,6 +59,10 @@ var/list/beam_master = list() icon_state = "emitter" damage = 0 // The actual damage is computed in /code/modules/power/singularity/emitter.dm + muzzle_type = /obj/effect/projectile/emitter/muzzle + tracer_type = /obj/effect/projectile/emitter/tracer + impact_type = /obj/effect/projectile/emitter/impact + /obj/item/projectile/beam/lastertag/blue name = "lasertag beam" icon_state = "bluelaser" @@ -123,6 +71,10 @@ var/list/beam_master = list() damage_type = BURN check_armour = "laser" + muzzle_type = /obj/effect/projectile/laser_blue/muzzle + tracer_type = /obj/effect/projectile/laser_blue/tracer + impact_type = /obj/effect/projectile/laser_blue/impact + /obj/item/projectile/beam/lastertag/blue/on_hit(var/atom/target, var/blocked = 0) if(istype(target, /mob/living/carbon/human)) var/mob/living/carbon/human/M = target @@ -153,6 +105,10 @@ var/list/beam_master = list() damage_type = BURN check_armour = "laser" + muzzle_type = /obj/effect/projectile/laser_omni/muzzle + tracer_type = /obj/effect/projectile/laser_omni/tracer + impact_type = /obj/effect/projectile/laser_omni/impact + /obj/item/projectile/beam/lastertag/omni/on_hit(var/atom/target, var/blocked = 0) if(istype(target, /mob/living/carbon/human)) var/mob/living/carbon/human/M = target @@ -168,6 +124,10 @@ var/list/beam_master = list() weaken = 3 stutter = 3 + muzzle_type = /obj/effect/projectile/xray/muzzle + tracer_type = /obj/effect/projectile/xray/tracer + impact_type = /obj/effect/projectile/xray/impact + /obj/item/projectile/beam/stun name = "stun beam" icon_state = "stun" @@ -175,3 +135,7 @@ var/list/beam_master = list() taser_effect = 1 agony = 40 damage_type = HALLOSS + + muzzle_type = /obj/effect/projectile/stun/muzzle + tracer_type = /obj/effect/projectile/stun/tracer + impact_type = /obj/effect/projectile/stun/impact diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index 627785343f5..5220ba54af2 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -8,6 +8,8 @@ embed = 1 sharp = 1 var/mob_passthrough_check = 0 + + muzzle_type = /obj/effect/projectile/bullet/muzzle /obj/item/projectile/bullet/on_hit(var/atom/target, var/blocked = 0) if (..(target, blocked)) From 11d340f89863b7d93c065d0da314e556777d5c5a Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Mon, 9 Mar 2015 20:40:10 +1300 Subject: [PATCH 6/6] Projectile effects - Part 3 - Fixes for turrets, rogue drones, and emitters - Removed muzzle flash from chem darts - Converted emitters to use spanclasses and have correct capitalization --- code/game/machinery/portable_turret.dm | 1 + code/game/machinery/turrets.dm | 1 + .../living/simple_animal/hostile/hostile.dm | 2 + code/modules/power/singularity/emitter.dm | 63 +++++++++---------- .../projectiles/guns/projectile/dartgun.dm | 2 + 5 files changed, 36 insertions(+), 33 deletions(-) diff --git a/code/game/machinery/portable_turret.dm b/code/game/machinery/portable_turret.dm index 2337f58ca60..a5f7bf0be0c 100644 --- a/code/game/machinery/portable_turret.dm +++ b/code/game/machinery/portable_turret.dm @@ -605,6 +605,7 @@ //Shooting Code: A.current = T + A.starting = T A.yo = U.y - T.y A.xo = U.x - T.x spawn(1) diff --git a/code/game/machinery/turrets.dm b/code/game/machinery/turrets.dm index 8f81c414def..965210ae155 100644 --- a/code/game/machinery/turrets.dm +++ b/code/game/machinery/turrets.dm @@ -265,6 +265,7 @@ A = new /obj/item/projectile/energy/electrode( loc ) use_power(200) A.current = T + A.starting = T A.yo = U.y - T.y A.xo = U.x - T.x spawn( 0 ) diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 03bf90b0f5f..73ff6eab5dd 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -188,6 +188,8 @@ del(A) return A.current = target + A.starting = get_turf(src) + A.original = get_turf(target) A.yo = target:y - start:y A.xo = target:x - start:x spawn( 0 ) diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index 59e93c23b64..c85c48e27a1 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -1,7 +1,7 @@ #define EMITTER_DAMAGE_POWER_TRANSFER 450 //used to transfer power to containment field generators /obj/machinery/power/emitter - name = "Emitter" + name = "emitter" desc = "It is a heavy duty industrial laser." icon = 'icons/obj/singularity.dmi' icon_state = "emitter" @@ -60,18 +60,18 @@ /obj/machinery/power/emitter/proc/activate(mob/user as mob) if(state == 2) if(!powernet) - user << "The emitter isn't connected to a wire." + user << "\The [src] isn't connected to a wire." return 1 if(!src.locked) if(src.active==1) src.active = 0 - user << "You turn off the [src]." + user << "You turn off [src]." message_admins("Emitter turned off by [key_name(user, user.client)](?) in ([x],[y],[z] - JMP)",0,1) log_game("Emitter turned off by [user.ckey]([user]) in ([x],[y],[z])") investigate_log("turned off by [user.key]","singulo") else src.active = 1 - user << "You turn on the [src]." + user << "You turn on [src]." src.shot_number = 0 src.fire_delay = 100 message_admins("Emitter turned on by [key_name(user, user.client)](?) in ([x],[y],[z] - JMP)",0,1) @@ -79,9 +79,9 @@ investigate_log("turned on by [user.key]","singulo") update_icon() else - user << "\red The controls are locked!" + user << "The controls are locked!" else - user << "\red The [src] needs to be firmly secured to the floor first." + user << "\The [src] needs to be firmly secured to the floor first." return 1 @@ -138,86 +138,83 @@ s.set_up(5, 1, src) s.start() A.set_dir(src.dir) + A.starting = get_turf(src) switch(dir) if(NORTH) - A.yo = 20 - A.xo = 0 + A.original = locate(x, y+1, z) if(EAST) - A.yo = 0 - A.xo = 20 + A.original = locate(x+1, y, z) if(WEST) - A.yo = 0 - A.xo = -20 + A.original = locate(x-1, y, z) else // Any other - A.yo = -20 - A.xo = 0 - A.process() //TODO: Carn: check this out + A.original = locate(x, y-1, z) + A.process() /obj/machinery/power/emitter/attackby(obj/item/W, mob/user) if(istype(W, /obj/item/weapon/wrench)) if(active) - user << "Turn off the [src] first." + user << "Turn off [src] first." return switch(state) if(0) state = 1 playsound(src.loc, 'sound/items/Ratchet.ogg', 75, 1) - user.visible_message("[user.name] secures [src.name] to the floor.", \ + user.visible_message("[user.name] secures [src] to the floor.", \ "You secure the external reinforcing bolts to the floor.", \ "You hear a ratchet") src.anchored = 1 if(1) state = 0 playsound(src.loc, 'sound/items/Ratchet.ogg', 75, 1) - user.visible_message("[user.name] unsecures [src.name] reinforcing bolts from the floor.", \ + user.visible_message("[user.name] unsecures [src] reinforcing bolts from the floor.", \ "You undo the external reinforcing bolts.", \ "You hear a ratchet") src.anchored = 0 if(2) - user << "\red The [src.name] needs to be unwelded from the floor." + user << "\The [src] needs to be unwelded from the floor." return if(istype(W, /obj/item/weapon/weldingtool)) var/obj/item/weapon/weldingtool/WT = W if(active) - user << "Turn off the [src] first." + user << "Turn off [src] first." return switch(state) if(0) - user << "\red The [src.name] needs to be wrenched to the floor." + user << "\The [src] needs to be wrenched to the floor." if(1) if (WT.remove_fuel(0,user)) playsound(src.loc, 'sound/items/Welder2.ogg', 50, 1) - user.visible_message("[user.name] starts to weld the [src.name] to the floor.", \ - "You start to weld the [src] to the floor.", \ + user.visible_message("[user.name] starts to weld [src] to the floor.", \ + "You start to weld [src] to the floor.", \ "You hear welding") if (do_after(user,20)) if(!src || !WT.isOn()) return state = 2 - user << "You weld the [src] to the floor." + user << "You weld [src] to the floor." connect_to_network() else - user << "\red You need more welding fuel to complete this task." + user << "You need more welding fuel to complete this task." if(2) if (WT.remove_fuel(0,user)) playsound(src.loc, 'sound/items/Welder2.ogg', 50, 1) - user.visible_message("[user.name] starts to cut the [src.name] free from the floor.", \ - "You start to cut the [src] free from the floor.", \ + user.visible_message("[user.name] starts to cut [src] free from the floor.", \ + "You start to cut [src] free from the floor.", \ "You hear welding") if (do_after(user,20)) if(!src || !WT.isOn()) return state = 1 - user << "You cut the [src] free from the floor." + user << "You cut [src] free from the floor." disconnect_from_network() else - user << "\red You need more welding fuel to complete this task." + user << "You need more welding fuel to complete this task." return if(istype(W, /obj/item/weapon/card/id) || istype(W, /obj/item/device/pda)) if(emagged) - user << "\red The lock seems to be broken" + user << "The lock seems to be broken." return if(src.allowed(user)) if(active) @@ -225,16 +222,16 @@ user << "The controls are now [src.locked ? "locked." : "unlocked."]" else src.locked = 0 //just in case it somehow gets locked - user << "\red The controls can only be locked when the [src] is online" + user << "The controls can only be locked when [src] is online." else - user << "\red Access denied." + user << "Access denied." return if(istype(W, /obj/item/weapon/card/emag) && !emagged) locked = 0 emagged = 1 - user.visible_message("[user.name] emags the [src.name].","\red You short out the lock.") + user.visible_message("[user.name] emags [src].","You short out the lock.") return ..() diff --git a/code/modules/projectiles/guns/projectile/dartgun.dm b/code/modules/projectiles/guns/projectile/dartgun.dm index afb1f33874c..a8f93b8d253 100644 --- a/code/modules/projectiles/guns/projectile/dartgun.dm +++ b/code/modules/projectiles/guns/projectile/dartgun.dm @@ -6,6 +6,8 @@ embed = 1 //the dart is shot fast enough to pierce space suits, so I guess splintering inside the target can be a thing. Should be rare due to low damage. var/reagent_amount = 15 kill_count = 15 //shorter range + + muzzle_type = null /obj/item/projectile/bullet/chemdart/New() reagents = new/datum/reagents(reagent_amount)