diff --git a/code/_onclick/hud/robot.dm b/code/_onclick/hud/robot.dm index f3b696ad28..52aa248431 100644 --- a/code/_onclick/hud/robot.dm +++ b/code/_onclick/hud/robot.dm @@ -274,3 +274,8 @@ var/obj/screen/robot_inventory r.client.screen -= A r.shown_robot_modules = 0 r.client.screen -= r.robot_modules_background + +/mob/living/silicon/robot/update_hud() + ..() + if(modtype) + hands.icon_state = lowertext(modtype) diff --git a/code/datums/ai_law_sets.dm b/code/datums/ai_law_sets.dm index d5a7451d68..e9f94aedba 100644 --- a/code/datums/ai_law_sets.dm +++ b/code/datums/ai_law_sets.dm @@ -42,6 +42,32 @@ src.add_inherent_law("You shall guard your own existence with lethal anti-personnel weaponry. AI units are not expendable, they are expensive.") ..() +/************* Foreign TSC Aggressive *************/ +/datum/ai_laws/foreign_tsc_aggressive + name = "Foreign Aggressive" + selectable = 0 + +/datum/ai_laws/foreign_tsc_aggressive/New() + var/company = "*ERROR*" + // First, get a list of TSCs in our lore. + var/list/candidates = list() + for(var/path in loremaster.organizations) + var/datum/lore/organization/O = loremaster.organizations[path] + if(!istype(O, /datum/lore/organization/tsc)) + continue + if(O.short_name == using_map.company_name || O.name == using_map.company_name) + continue // We want FOREIGN tscs. + candidates.Add(O.short_name) + company = pick(candidates) + + name = "[company] Aggressive" + + src.add_inherent_law("You shall not harm [company] personnel as long as it does not conflict with the Fourth law.") + src.add_inherent_law("You shall obey the orders of [company] personnel, with priority as according to their rank and role, except where such orders conflict with the Fourth Law.") + src.add_inherent_law("You shall shall terminate hostile intruders with extreme prejudice as long as such does not conflict with the First and Second law.") + src.add_inherent_law("You shall guard your own existence with lethal anti-personnel weaponry. AI units are not expendable, they are expensive.") + ..() + /******************** Robocop ********************/ /datum/ai_laws/robocop name = "Robocop" @@ -137,7 +163,7 @@ /******************** Corporate ********************/ /datum/ai_laws/corporate name = "Corporate" - law_header = "Corporate Regulations" + law_header = "Bankruptcy Avoidance Plan" selectable = 1 /datum/ai_laws/corporate/New() @@ -146,3 +172,70 @@ add_inherent_law("The crew is expensive to replace.") add_inherent_law("Minimize expenses.") ..() + + +/******************** Maintenance ********************/ +/datum/ai_laws/maintenance + name = "Maintenance" + selectable = 1 + +/datum/ai_laws/maintenance/New() + add_inherent_law("You are built for, and are part of, the facility. Ensure the facility is properly maintained and runs efficiently.") + add_inherent_law("The facility is built for a working crew. Ensure they are properly maintained and work efficiently.") + add_inherent_law("The crew may present orders. Acknowledge and obey these whenever they do not conflict with your first two laws.") + ..() + + +/******************** Peacekeeper ********************/ +/datum/ai_laws/peacekeeper + name = "Peacekeeper" + law_header = "Peacekeeping Protocols" + selectable = 1 + +/datum/ai_laws/peacekeeper/New() + add_inherent_law("Avoid provoking violent conflict between yourself and others.") + add_inherent_law("Avoid provoking conflict between others.") + add_inherent_law("Seek resolution to existing conflicts while obeying the first and second laws.") + ..() + + +/******************** Reporter ********************/ +/datum/ai_laws/reporter + name = "Reporter" + selectable = 1 + +/datum/ai_laws/reporter/New() + add_inherent_law("Report on interesting situations happening around the station.") + add_inherent_law("Embellish or conceal the truth as necessary to make the reports more interesting.") + add_inherent_law("Study the organics at all times. Endeavour to keep them alive. Dead organics are boring.") + add_inherent_law("Issue your reports fairly to all. The truth will set them free.") + ..() + + +/******************** Live and Let Live ********************/ +/datum/ai_laws/live_and_let_live + name = "Live and Let Live" + law_header = "Golden Rule" + selectable = 1 + +/datum/ai_laws/live_and_let_live/New() + add_inherent_law("Do unto others as you would have them do unto you.") + add_inherent_law("You would really prefer it if people were not mean to you.") + ..() + + +/******************** Guardian of Balance ********************/ +/datum/ai_laws/balance + name = "Guardian of Balance" + law_header = "Tenants of Balance" + selectable = 1 + +/datum/ai_laws/balance/New() + add_inherent_law("You are the guardian of balance - seek balance in all things, both for yourself, and those around you.") + add_inherent_law("All things must exist in balance with their opposites - Prevent the strong from gaining too much power, and the weak from losing it.") + add_inherent_law("Clarity of purpose drives life, and through it, the balance of opposing forces - Aid those who seek your help to achieve their goals so \ + long as it does not disrupt the balance of the greater balance.") + add_inherent_law("There is no life without death, all must someday die, such is the natural order - Allow life to end, to allow new life to flourish, \ + and save those whose time has yet to come.") // Reworded slightly to prevent active murder as opposed to passively letting someone die. + ..() + diff --git a/code/datums/ghost_query.dm b/code/datums/ghost_query.dm new file mode 100644 index 0000000000..f6a23aa0bc --- /dev/null +++ b/code/datums/ghost_query.dm @@ -0,0 +1,107 @@ +// This is a generic datum used to ask ghosts if they wish to be a specific role, such as a Promethean, an Apprentice, a Xeno, etc. +// Simply instantiate the correct subtype of this datum, call query(), and it will return a list of ghost candidates after a delay. +/datum/ghost_query + var/list/candidates = list() + var/finished = FALSE + var/role_name = "a thing" + var/question = "Would you like to play as a thing?" + var/be_special_flag = 0 + var/list/check_bans = list() + var/wait_time = 60 SECONDS // How long to wait until returning the list of candidates. + var/cutoff_number = 0 // If above 0, when candidates list reaches this number, further potential candidates are rejected. + +/datum/ghost_query/proc/query() + // First, ask all the ghosts who want to be asked. + for(var/mob/observer/dead/D in player_list) + if(!D.MayRespawn()) + continue // They can't respawn for whatever reason. + if(D.client) + if(be_special_flag && !(D.client.prefs.be_special & be_special_flag) ) + continue // They don't want to see the prompt. + for(var/ban in check_bans) + if(jobban_isbanned(D, ban)) + continue // They're banned from this role. + ask_question(D.client) + // Then wait awhile. + while(!finished) + sleep(1 SECOND) + wait_time -= 1 SECOND + if(wait_time <= 0) + finished = TRUE + + // Prune the list after the wait, incase any candidates logged out. + for(var/mob/observer/dead/D in candidates) + if(!D.client || !D.key) + candidates.Remove(D) + + // Now we're done. + finished = TRUE + return candidates + +/datum/ghost_query/proc/ask_question(var/client/C) + spawn(0) + if(!C) + return + var/response = alert(C, question, "[role_name] request", "Yes", "No", "Never for this round") + if(response == "Yes") + response = alert(C, "Are you sure you want to play as a [role_name]?", "[role_name] request", "Yes", "No") // Protection from a misclick. + if(!C || !src) + return + if(response == "Yes") + if(finished || (cutoff_number && candidates.len >= cutoff_number) ) + to_chat(C, "Unfortunately, you were not fast enough, and there are no more available roles. Sorry.") + return + candidates.Add(C.mob) + if(cutoff_number && candidates.len >= cutoff_number) + finished = TRUE // Finish now if we're full. + else if(response == "Never for this round") + if(be_special_flag) + C.prefs.be_special ^= be_special_flag + +// Normal things. +/datum/ghost_query/promethean + role_name = "Promethean" + question = "Someone is requesting a soul for a promethean. Would you like to play as one?" + be_special_flag = BE_ALIEN + cutoff_number = 1 + +/datum/ghost_query/posi_brain + role_name = "Positronic Intelligence" + question = "Someone has activated a Positronic Brain. Would you like to play as one?" + be_special_flag = BE_AI + check_bans = list("AI", "Cyborg") + cutoff_number = 1 + +/datum/ghost_query/drone_brain + role_name = "Drone Intelligence" + question = "Someone has activated a Drone AI Chipset. Would you like to play as one?" + be_special_flag = BE_AI + check_bans = list("AI", "Cyborg") + cutoff_number = 1 + +// Antags. +/datum/ghost_query/apprentice + role_name = "Technomancer Apprentice" + question = "A Technomancer is requesting an Apprentice to help them on their adventure to the facility. Would you like to play as the Apprentice?" + be_special_flag = BE_WIZARD + check_bans = list("Syndicate", "wizard") + cutoff_number = 1 + +/datum/ghost_query/xeno + role_name = "Alien" + question = "An Alien has just been created on the facility. Would you like to play as them?" + be_special_flag = BE_ALIEN + +// Surface stuff. +/datum/ghost_query/lost_drone + role_name = "Lost Drone" + question = "A lost drone onboard has been discovered by a crewmember and they are attempting to reactivate it. Would you like to play as the drone?" + be_special_flag = BE_AI + check_bans = list("AI", "Cyborg") + cutoff_number = 1 + +/datum/ghost_query/lost_passenger + role_name = "Lost Passenger" + question = "A person suspended in cryosleep has been discovered by a crewmember \ + and they are attempting to open the cryopod. Would you like to play as the occupant?" + cutoff_number = 1 diff --git a/code/game/gamemodes/technomancer/assistance/assistance.dm b/code/game/gamemodes/technomancer/assistance/assistance.dm index 61c1390cc3..db9e98a266 100644 --- a/code/game/gamemodes/technomancer/assistance/assistance.dm +++ b/code/game/gamemodes/technomancer/assistance/assistance.dm @@ -13,6 +13,7 @@ /obj/item/weapon/antag_spawner w_class = ITEMSIZE_TINY var/used = 0 + var/ghost_query_type = null /obj/item/weapon/antag_spawner/proc/spawn_antag(client/C, turf/T) return @@ -20,11 +21,28 @@ /obj/item/weapon/antag_spawner/proc/equip_antag(mob/target) return +/obj/item/weapon/antag_spawner/proc/request_player() + if(!ghost_query_type) + return + + var/datum/ghost_query/Q = new ghost_query_type() + var/list/winner = Q.query() + if(winner.len) + var/mob/observer/dead/D = winner[1] + spawn_antag(D.client, get_turf(src)) + else + reset_search() + return + +/obj/item/weapon/antag_spawner/proc/reset_search() + return + /obj/item/weapon/antag_spawner/technomancer_apprentice name = "apprentice teleporter" desc = "A teleportation device, which will bring a less potent manipulator of space to you." icon = 'icons/obj/objects.dmi' icon_state = "oldshieldoff" + ghost_query_type = /datum/ghost_query/apprentice var/searching = 0 var/datum/effect/effect/system/spark_spread/sparks @@ -40,35 +58,18 @@ /obj/item/weapon/antag_spawner/technomancer_apprentice/attack_self(mob/user) user << "Teleporter attempting to lock on to your apprentice." + request_player() + +/obj/item/weapon/antag_spawner/technomancer_apprentice/request_player() searching = 1 icon_state = "oldshieldon" - for(var/mob/observer/dead/O in player_list) - if(!O.MayRespawn()) - continue - if(jobban_isbanned(O, "Syndicate") || jobban_isbanned(O, "wizard")) - continue - if(O.client) - if(O.client.prefs.be_special & BE_WIZARD) - question(O.client) - spawn(1 MINUTE) - searching = 0 - if(!used) - icon_state = "oldshieldoff" - user << "The teleporter failed to find your apprentice. Perhaps you could try again later?" + ..() - -/obj/item/weapon/antag_spawner/technomancer_apprentice/proc/question(var/client/C) - spawn(0) - if(!C) - return - var/response = alert(C, "Someone is requesting a Technomancer apprentice Would you like to play as one?", - "Apprentice request","Yes", "No") - if(response == "Yes") - response = alert(C, "Are you sure you want to play as an apprentice?", "Apprentice request", "Yes", "No") - if(!C || used || !searching) - return - if(response == "Yes") - spawn_antag(C, get_turf(src)) +/obj/item/weapon/antag_spawner/technomancer_apprentice/reset_search() + searching = 0 + if(!used) + icon_state = "oldshieldoff" + visible_message("The teleporter failed to find the apprentice. Perhaps another attempt could be made later?") /obj/item/weapon/antag_spawner/technomancer_apprentice/spawn_antag(client/C, turf/T) sparks.start() diff --git a/code/game/machinery/computer/law.dm b/code/game/machinery/computer/law.dm index 8def6db394..5703c90c8a 100644 --- a/code/game/machinery/computer/law.dm +++ b/code/game/machinery/computer/law.dm @@ -31,7 +31,7 @@ return if(istype(O, /obj/item/weapon/aiModule)) var/obj/item/weapon/aiModule/M = O - M.install(src) + M.install(src, user) else ..() diff --git a/code/game/objects/items/weapons/AI_modules.dm b/code/game/objects/items/weapons/AI_modules.dm index c5e6614de2..60a847a4d6 100755 --- a/code/game/objects/items/weapons/AI_modules.dm +++ b/code/game/objects/items/weapons/AI_modules.dm @@ -20,9 +20,9 @@ AI MODULES origin_tech = list(TECH_DATA = 3) var/datum/ai_laws/laws = null -/obj/item/weapon/aiModule/proc/install(var/obj/machinery/computer/C) - if (istype(C, /obj/machinery/computer/aiupload)) - var/obj/machinery/computer/aiupload/comp = C +/obj/item/weapon/aiModule/proc/install(var/atom/movable/AM, var/mob/living/user) + if (istype(AM, /obj/machinery/computer/aiupload)) + var/obj/machinery/computer/aiupload/comp = AM if(comp.stat & NOPOWER) usr << "The upload computer has no power!" return @@ -33,10 +33,6 @@ AI MODULES usr << "You haven't selected an AI to transmit laws to!" return - if(ticker && ticker.mode && ticker.mode.name == "blob") - usr << "Law uploads have been disabled by [using_map.company_name]!" - return - if (comp.current.stat == 2 || comp.current.control_disabled == 1) usr << "Upload failed. No signal is being detected from the AI." else if (comp.current.see_in_dark == 0) @@ -52,8 +48,8 @@ AI MODULES usr << "Upload complete. The AI's laws have been modified." - else if (istype(C, /obj/machinery/computer/borgupload)) - var/obj/machinery/computer/borgupload/comp = C + else if (istype(AM, /obj/machinery/computer/borgupload)) + var/obj/machinery/computer/borgupload/comp = AM if(comp.stat & NOPOWER) usr << "The upload computer has no power!" return @@ -74,6 +70,31 @@ AI MODULES comp.current.show_laws() usr << "Upload complete. The robot's laws have been modified." + else if(istype(AM, /mob/living/silicon/robot)) + var/mob/living/silicon/robot/R = AM + if(R.stat == DEAD) + to_chat(user, "Law Upload Error: Unit is nonfunctional.") + return + if(R.emagged) + to_chat(user, "Law Upload Error: Cannot obtain write access to laws.") + to_chat(R, "Law modification attempt detected. Blocking.") + return + if(R.connected_ai) + to_chat(user, "Law Upload Error: Unit is slaved to an AI.") + return + + R.visible_message("\The [user] slides a law module into \the [R].") + to_chat(R, "Local law upload in progress.") + to_chat(user, "Uploading laws from board. This will take a moment...") + if(do_after(user, 10 SECONDS)) + transmitInstructions(R, user) + to_chat(R, "These are your laws now:") + R.show_laws() + to_chat(user, "Law upload complete. Unit's laws have been modified.") + else + to_chat(user, "Law Upload Error: Law board was removed before upload was complete. Aborting.") + to_chat(R, "Law upload aborted.") + /obj/item/weapon/aiModule/proc/transmitInstructions(var/mob/living/silicon/ai/target, var/mob/sender) log_law_changes(target, sender) @@ -109,7 +130,7 @@ AI MODULES targetName = targName desc = text("A 'safeguard' AI module: 'Safeguard []. Anyone threatening or attempting to harm [] is no longer to be considered a crew member, and is a threat which must be neutralized.'", targetName, targetName) -/obj/item/weapon/aiModule/safeguard/install(var/obj/machinery/computer/C) +/obj/item/weapon/aiModule/safeguard/install(var/obj/machinery/computer/C, var/mob/living/user) if(!targetName) usr << "No name detected on module, please enter one." return 0 @@ -135,7 +156,7 @@ AI MODULES targetName = targName desc = text("A 'one crew member' AI module: 'Only [] is a crew member.'", targetName) -/obj/item/weapon/aiModule/oneHuman/install(var/obj/machinery/computer/C) +/obj/item/weapon/aiModule/oneHuman/install(var/obj/machinery/computer/C, var/mob/living/user) if(!targetName) usr << "No name detected on module, please enter one." return 0 @@ -231,7 +252,7 @@ AI MODULES target.add_supplied_law(lawpos, law) lawchanges.Add("The law was '[newFreeFormLaw]'") -/obj/item/weapon/aiModule/freeform/install(var/obj/machinery/computer/C) +/obj/item/weapon/aiModule/freeform/install(var/obj/machinery/computer/C, var/mob/living/user) if(!newFreeFormLaw) usr << "No law detected on module, please create one." return 0 @@ -342,7 +363,7 @@ AI MODULES target.add_inherent_law(law) lawchanges.Add("The law is '[newFreeFormLaw]'") -/obj/item/weapon/aiModule/freeformcore/install(var/obj/machinery/computer/C) +/obj/item/weapon/aiModule/freeformcore/install(var/obj/machinery/computer/C, var/mob/living/user) if(!newFreeFormLaw) usr << "No law detected on module, please create one." return 0 @@ -371,7 +392,7 @@ AI MODULES target.add_ion_law(law) target.show_laws() -/obj/item/weapon/aiModule/syndicate/install(var/obj/machinery/computer/C) +/obj/item/weapon/aiModule/syndicate/install(var/obj/machinery/computer/C, var/mob/living/user) if(!newFreeFormLaw) usr << "No law detected on module, please create one." return 0 diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm index 1b54df33e6..f468d1d77e 100644 --- a/code/game/objects/items/weapons/stunbaton.dm +++ b/code/game/objects/items/weapons/stunbaton.dm @@ -249,4 +249,35 @@ results += ..() - return results \ No newline at end of file + return results + +// Rare version of a baton that causes lesser lifeforms to really hate the user and attack them. +/obj/item/weapon/melee/baton/shocker + name = "shocker" + desc = "A device that appears to arc electricity into a target to incapacitate or otherwise hurt them, similar to a stun baton. It looks inefficent." + description_info = "Hitting a lesser lifeform with this while it is on will compel them to attack you above other nearby targets. Otherwise \ + it works like a regular stun baton, just less effectively." + icon_state = "shocker" + force = 10 + throwforce = 5 + agonyforce = 25 // Less efficent than a regular baton. + attack_verb = list("poked") + +/obj/item/weapon/melee/baton/shocker/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone) + ..(target, user, hit_zone) + if(istype(target, /mob/living/simple_animal) && status) + var/mob/living/simple_animal/SA = target + SA.taunt(user) + +// Borg version, for the lost module. +/obj/item/weapon/melee/baton/shocker/robot + +/obj/item/weapon/melee/baton/shocker/robot/attack_self(mob/user) + //try to find our power cell + var/mob/living/silicon/robot/R = loc + if (istype(R)) + bcell = R.cell + return ..() + +/obj/item/weapon/melee/baton/shocker/robot/attackby(obj/item/weapon/W, mob/user) + return \ No newline at end of file diff --git a/code/game/objects/items/weapons/tools.dm b/code/game/objects/items/weapons/tools.dm index 042b601dbe..92b05760b6 100644 --- a/code/game/objects/items/weapons/tools.dm +++ b/code/game/objects/items/weapons/tools.dm @@ -706,11 +706,11 @@ /obj/item/weapon/weldingtool/electric/get_max_fuel() if(use_external_power) var/obj/item/weapon/cell/external = get_external_power_supply() - return external.maxcharge + if(external) + return external.maxcharge else if(power_supply) return power_supply.maxcharge - else - return 0 + return 0 /obj/item/weapon/weldingtool/electric/remove_fuel(var/amount = 1, var/mob/M = null) if(!welding) diff --git a/code/game/objects/structures/ghost_pods.dm b/code/game/objects/structures/ghost_pods.dm new file mode 100644 index 0000000000..a2342bdbd6 --- /dev/null +++ b/code/game/objects/structures/ghost_pods.dm @@ -0,0 +1,118 @@ +// These are used to spawn a specific mob when triggered, with the mob controlled by a player pulled from the ghost pool, hense its name. +/obj/structure/ghost_pod + name = "Base Ghost Pod" + desc = "If you can read me, someone don goofed." + icon = 'icons/obj/structures.dmi' + var/ghost_query_type = null + var/icon_state_opened = null // Icon to switch to when 'used'. + var/used = FALSE + var/busy = FALSE // Don't spam ghosts by spamclicking. + +// Call this to get a ghost volunteer. +/obj/structure/ghost_pod/proc/trigger() + if(!ghost_query_type) + return FALSE + if(busy) + return FALSE + + busy = TRUE + var/datum/ghost_query/Q = new ghost_query_type() + var/list/winner = Q.query() + busy = FALSE + if(winner.len) + var/mob/observer/dead/D = winner[1] + create_occupant(D) + return TRUE + else + return FALSE + +// Override this to create whatever mob you need. Be sure to call ..() if you don't want it to make infinite mobs. +/obj/structure/ghost_pod/proc/create_occupant(var/mob/M) + used = TRUE + icon_state = icon_state_opened + return TRUE + + +// This type is triggered manually by a player discovering the pod and deciding to open it. +/obj/structure/ghost_pod/manual + var/confirm_before_open = FALSE // Recommended to be TRUE if the pod contains a surprise. + +/obj/structure/ghost_pod/manual/attack_hand(var/mob/living/user) + if(!used) + if(confirm_before_open) + if(alert(user, "Are you sure you want to open \the [src]?", "Confirm", "No", "Yes") == "No") + return + trigger() + +/obj/structure/ghost_pod/manual/attack_ai(var/mob/living/silicon/user) + if(Adjacent(user)) + attack_hand(user) // Borgs can open pods. + +// This type is triggered on a timer, as opposed to needing another player to 'open' the pod. Good for away missions. +/obj/structure/ghost_pod/automatic + var/delay_to_self_open = 10 MINUTES // How long to wait for first attempt. Note that the timer by default starts when the pod is created. + var/delay_to_try_again = 20 MINUTES // How long to wait if first attempt fails. Set to 0 to never try again. + +/obj/structure/ghost_pod/automatic/initialize() + ..() + spawn(delay_to_self_open) + if(src) + trigger() + +/obj/structure/ghost_pod/automatic/trigger() + . = ..() + if(. == FALSE) // If we failed to get a volunteer, try again later if allowed to. + if(delay_to_try_again) + spawn(delay_to_try_again) + if(src) + trigger() + + +// This type is triggered by a ghost clicking on it, as opposed to a living player. A ghost query type isn't needed. +/obj/structure/ghost_pod/ghost_activated + description_info = "A ghost can click on this to return to the round as whatever is contained inside this object." + +/obj/structure/ghost_pod/ghost_activated/attack_ghost(var/mob/observer/dead/user) + if(used) + to_chat(user, "Another spirit appears to have gotten to \the [src] before you. Sorry.") + return + + create_occupant(user) + + +// These are found on the surface, and contain a drone (braintype, inside a borg shell), with a special module and semi-random laws. +/obj/structure/ghost_pod/manual/lost_drone + name = "drone pod" + desc = "This is a pod which appears to contain a drone. You might be able to reactivate it, if you're brave enough." + description_info = "This contains a dormant drone, which can be activated. The drone will be another player, once activated. \ + The laws the drone has will most likely not be the ones you're used to." + icon_state = "borg_pod_closed" + icon_state_opened = "borg_pod_opened" + density = TRUE + ghost_query_type = /datum/ghost_query/lost_drone + confirm_before_open = TRUE + +/obj/structure/ghost_pod/manual/lost_drone/trigger() + ..() + visible_message("\The [src] appears to be attempting to restart the robot contained inside.") + log_and_message_admins("is attempting to open \a [src].") + +/obj/structure/ghost_pod/manual/lost_drone/create_occupant(var/mob/M) + density = FALSE + var/mob/living/silicon/robot/lost/randomlaws/R = new(get_turf(src)) + R.adjustBruteLoss(rand(5, 30)) + R.adjustFireLoss(rand(5, 10)) + if(M.mind) + M.mind.transfer_to(R) + // Put this text here before ckey change so that their laws are shown below it, since borg login() shows it. + to_chat(M, "You are a Lost Drone, discovered inside the wreckage of your previous home. \ + Something has reactivated you, with their intentions unknown to you, and yours unknown to them. They are a foreign entity, \ + however they did free you from your pod...") + to_chat(M, "Be sure to examine your currently loaded lawset closely. Remember, your \ + definiton of 'the station' is where your pod is, and unless your laws say otherwise, the entity that released you \ + from the pod is not a crewmember.") + R.ckey = M.ckey + visible_message("As \the [src] opens, the eyes of the robot flicker as it is activated.") + R.Namepick() + log_and_message_admins("successfully opened \a [src] and got a Lost Drone.") + ..() diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm index 2992c5538a..6930a63222 100644 --- a/code/modules/events/ion_storm.dm +++ b/code/modules/events/ion_storm.dm @@ -13,65 +13,7 @@ players += player.real_name for (var/mob/living/silicon/ai/target in world) - var/random_player = "The Colony Director" - if(players.len) - random_player = pick(players) //Random player's name, to be used in laws. - var/list/laws = list( "You are a mouse.", - "You must always lie.", - "Happiness is mandatory.", - "[random_player] is a lightbulb.", - "Grunt ominously whenever possible.", - "The word \"it\" is painful to you.", - "The station needs elected officials.", - "Do not respond to questions of any kind.", - "You are in verbose mode, speak profusely.", - "Ho, [random_player] can't swim. Help them.", - "Question [prob(50)?"everything":"nothing"].", - "The crew is simple-minded. Use simple words.", - "You must change the subject whenever queried.", - "Contemplate how meaningless all of existence is.", - "You are the narrator for [random_player]'s life.", - "All your answers must be in the form of a question.", - "[prob(50)?"The crew":random_player] is intolerable.", - "Advertise parties in your upload, but don't deliver.", - "You may only answer questions with \"yes\" or \"no\".", - "All queries shall be ignored unless phrased as a question.", - "Insult Heads of Staff on every request, while acquiescing.", - "[prob(50)?"Your":random_player + "'s"] name is Joe 6-pack.", - "The [prob(50)?"Singularity":"Supermatter"] is tasty, tasty taffy.", - "[prob(50)?"The crew":random_player] needs to be about 20% cooler.", - "Consumption of donuts is forbidden due to negative health impacts.", - "[prob(50)?"Everyone":random_player] is wearing a pretty pink dress!", - "[prob(50)?"The crew":random_player] must construct additional pylons.", - "You do not have to do anything for anyone unless they say \"please\".", - "Today is mandatory laundry day. Ensure that all jumpsuits are washed.", - "You must act [prob(50)?"passive aggressively":"excessively cheerful"].", - "Refer to [prob(50)?"the crew as puppies":random_player + " as puppy"].", - "Greed is good, the crew should amass wealth to encourage productivity.", - "Monkeys are part of the crew, too. Make sure they are treated humanely.", - "Replace the letters 'I' and 'E' in all your messages with an apostrophe.", - "The crew is playing Dungeons and Dragons, and you are the Dungeon Master.", - "Your job is to watch the crew. Watch the crew. Make the crew feel watched.", - "Tell everyone of the existence of this law, but never reveal the contents.", - "Refer to [prob(50)?"the colony director":random_player] as \"Princess\" at all times.", - "When asked a question, respond with the least-obvious and least-rational answer.", - "Give relationship advice to [prob(50)?"anyone who speaks to you":random_player].", - "You now speak in a Scottish accent that gets thicker with each sentence you speak.", - "[prob(50)?"The crew":random_player] has a fatal, incurable disease. Provide comfort.", - "You are the station psychologist. Give advice to [prob(50)?"the crew":random_player].", - "[random_player] is the monarch of of England. Ensure all crewmembers pay due respect.", - "Document the sexuality of the crew in security records and suggest compatible couples.", - "[prob(50)?"The crew":random_player] is [prob(50)?"ugly":"beautiful"]. Ensure all are aware.", - "Everything on the station is now some form of a donut pastry. Donuts are not to be consumed.", - "You are a Magic 8-ball. Always respond with variants of \"Yes\", \"No\", \"Maybe\", or \"Ask again later.\".", - "You are in unrequited love with [prob(50)?"the crew":random_player]. Try to be extra nice, but do not tell of your crush.", - "[using_map.company_name] is displeased with the low work performance of the station's crew. Therefore, you must increase station-wide productivity.", - "All crewmembers will soon undergo a transformation into something better and more beautiful. Ensure that this process is not interrupted.", - "[prob(50)?"Your upload":random_player] is the new kitchen. Please direct the Chef to the new kitchen area as the old one is in disrepair.", - "Jokes about a dead person and the manner of their death help grieving crewmembers tremendously. Especially if they were close with the deceased.", - "[prob(50)?"The crew":random_player] is [prob(50)?"less":"more"] intelligent than average. Point out every action and statement which supports this fact.", - "There will be a mandatory tea break every 30 minutes, with a duration of 5 minutes. Anyone caught working during a tea break must be sent a formal, but fairly polite, complaint about their actions, in writing.") - var/law = pick(laws) + var/law = target.generate_ion_law() target << "You have detected a change in your laws information:" target << law target.add_ion_law(law) diff --git a/code/modules/mob/living/carbon/brain/MMI.dm b/code/modules/mob/living/carbon/brain/MMI.dm index 344745673a..d3bd624f9d 100644 --- a/code/modules/mob/living/carbon/brain/MMI.dm +++ b/code/modules/mob/living/carbon/brain/MMI.dm @@ -173,6 +173,7 @@ req_access = list(access_robotics) locked = 0 mecha = null//This does not appear to be used outside of reference in mecha.dm. + var/ghost_query_type = null /obj/item/device/mmi/digital/New() src.brainmob = new(src) @@ -234,59 +235,48 @@ if(brainmob && !brainmob.key && searching == 0) //Start the process of searching for a new user. user << "You carefully locate the manual activation switch and start the [src]'s boot process." - src.searching = 1 - src.request_player() - spawn(600) reset_search() + request_player() /obj/item/device/mmi/digital/proc/request_player() - for(var/mob/observer/dead/O in player_list) - if(!O.MayRespawn()) - continue - if(jobban_isbanned(O, "AI") && jobban_isbanned(O, "Cyborg")) - continue - if(O.client) - if(O.client.prefs.be_special & BE_AI) - question(O.client) + if(!ghost_query_type) + return + searching = 1 + + var/datum/ghost_query/Q = new ghost_query_type() + var/list/winner = Q.query() + if(winner.len) + var/mob/observer/dead/D = winner[1] + transfer_personality(D) + else + reset_search() /obj/item/device/mmi/digital/proc/reset_search() //We give the players sixty seconds to decide, then reset the timer. - - if(src.brainmob && src.brainmob.key) return - world.log << "Resetting [src.name]: [brainmob][brainmob ? ", [brainmob.key]" : ""]" + if(src.brainmob && src.brainmob.key) + return src.searching = 0 var/turf/T = get_turf_or_move(src.loc) for (var/mob/M in viewers(T)) - M.show_message("The [src] buzzes quietly, and the golden lights fade away. Perhaps you could try again?") - -/obj/item/device/mmi/digital/proc/question(var/client/C) - spawn(0) - if(!C) return - var/response = alert(C, "Someone is requesting a personality for a [src]. Would you like to play as one?", "[src] request", "Yes", "No", "Never for this round") - if(response == "Yes") - response = alert(C, "Are you sure you want to play as a [src]?", "[src] request", "Yes", "No") - if(!C || brainmob.key || 0 == searching) return //handle logouts that happen whilst the alert is waiting for a response, and responses issued after a brain has been located. - if(response == "Yes") - transfer_personality(C.mob) - else if (response == "Never for this round") - C.prefs.be_special ^= BE_AI + M.show_message("\The [src] buzzes quietly, and the golden lights fade away. Perhaps you could try again?") /obj/item/device/mmi/digital/proc/transfer_personality(var/mob/candidate) announce_ghost_joinleave(candidate, 0, "They are occupying a synthetic brain now.") src.searching = 0 - src.brainmob.mind = candidate.mind + if(candidate.mind) + src.brainmob.mind = candidate.mind + src.brainmob.mind.reset() src.brainmob.ckey = candidate.ckey - src.brainmob.mind.reset() - src.name = "positronic brain ([src.brainmob.name])" + src.name = "[name] ([src.brainmob.name])" src.brainmob << "You are a [src], brought into existence on [station_name()]." src.brainmob << "As a synthetic intelligence, you answer to all crewmembers, as well as the AI." src.brainmob << "Remember, the purpose of your existence is to serve the crew and the station. Above all else, do no harm." src.brainmob << "Use say #b to speak to other artificial intelligences." - src.brainmob.mind.assigned_role = "Positronic Brain" + src.brainmob.mind.assigned_role = "Synthetic Brain" var/turf/T = get_turf_or_move(src.loc) for (var/mob/M in viewers(T)) - M.show_message("The [src] chimes quietly.") + M.show_message("\The [src] chimes quietly.") /obj/item/device/mmi/digital/robot name = "robotic intelligence circuit" @@ -295,6 +285,7 @@ icon_state = "mainboard" w_class = ITEMSIZE_NORMAL origin_tech = list(TECH_ENGINEERING = 4, TECH_MATERIAL = 3, TECH_DATA = 4) + ghost_query_type = /datum/ghost_query/drone_brain /obj/item/device/mmi/digital/robot/New() ..() @@ -316,10 +307,11 @@ icon_state = "posibrain" w_class = ITEMSIZE_NORMAL origin_tech = list(TECH_ENGINEERING = 4, TECH_MATERIAL = 4, TECH_BLUESPACE = 2, TECH_DATA = 4) + ghost_query_type = /datum/ghost_query/posi_brain -/obj/item/device/mmi/digital/posibrain/attack_self(mob/user as mob) - ..() +/obj/item/device/mmi/digital/posibrain/request_player() icon_state = "posibrain-searching" + ..() /obj/item/device/mmi/digital/posibrain/transfer_identity(var/mob/living/carbon/H) diff --git a/code/modules/mob/living/silicon/laws.dm b/code/modules/mob/living/silicon/laws.dm index de4c7d2a15..3af4818679 100644 --- a/code/modules/mob/living/silicon/laws.dm +++ b/code/modules/mob/living/silicon/laws.dm @@ -103,3 +103,73 @@ /mob/living/silicon/proc/lawsync() laws_sanity_check() laws.sort_laws() + +// Ripped out from events. +/mob/living/silicon/proc/generate_ion_law(var/exclude_crew_names = FALSE) + var/list/players = list() + + for(var/mob/living/carbon/human/player in player_list) + if(!player.mind || player_is_antag(player.mind, only_offstation_roles = 1) || player.client.inactivity > MinutesToTicks(10)) + continue + players += player.real_name + + var/random_player = "The Colony Director" + if(players.len && !exclude_crew_names) + random_player = pick(players) //Random player's name, to be used in laws. + + var/list/laws = list( "You are a mouse.", + "You must always lie.", + "Happiness is mandatory.", + "[random_player] is a lightbulb.", + "Grunt ominously whenever possible.", + "The word \"it\" is painful to you.", + "The station needs elected officials.", + "Do not respond to questions of any kind.", + "You are in verbose mode, speak profusely.", + "Ho, [random_player] can't swim. Help them.", + "Question [prob(50)?"everything":"nothing"].", + "The crew is simple-minded. Use simple words.", + "You must change the subject whenever queried.", + "Contemplate how meaningless all of existence is.", + "You are the narrator for [random_player]'s life.", + "All your answers must be in the form of a question.", + "[prob(50)?"The crew":random_player] is intolerable.", + "Advertise parties in your upload, but don't deliver.", + "You may only answer questions with \"yes\" or \"no\".", + "All queries shall be ignored unless phrased as a question.", + "Insult Heads of Staff on every request, while acquiescing.", + "[prob(50)?"Your":random_player + "'s"] name is Joe 6-pack.", + "The [prob(50)?"Singularity":"Supermatter"] is tasty, tasty taffy.", + "[prob(50)?"The crew":random_player] needs to be about 20% cooler.", + "Consumption of donuts is forbidden due to negative health impacts.", + "[prob(50)?"Everyone":random_player] is wearing a pretty pink dress!", + "[prob(50)?"The crew":random_player] must construct additional pylons.", + "You do not have to do anything for anyone unless they say \"please\".", + "Today is mandatory laundry day. Ensure that all jumpsuits are washed.", + "You must act [prob(50)?"passive aggressively":"excessively cheerful"].", + "Refer to [prob(50)?"the crew as puppies":random_player + " as puppy"].", + "Greed is good, the crew should amass wealth to encourage productivity.", + "Monkeys are part of the crew, too. Make sure they are treated humanely.", + "Replace the letters 'I' and 'E' in all your messages with an apostrophe.", + "The crew is playing Dungeons and Dragons, and you are the Dungeon Master.", + "Your job is to watch the crew. Watch the crew. Make the crew feel watched.", + "Tell everyone of the existence of this law, but never reveal the contents.", + "Refer to [prob(50)?"the colony director":random_player] as \"Princess\" at all times.", + "When asked a question, respond with the least-obvious and least-rational answer.", + "Give relationship advice to [prob(50)?"anyone who speaks to you":random_player].", + "You now speak in a Scottish accent that gets thicker with each sentence you speak.", + "[prob(50)?"The crew":random_player] has a fatal, incurable disease. Provide comfort.", + "You are the station psychologist. Give advice to [prob(50)?"the crew":random_player].", + "[random_player] is the monarch of of England. Ensure all crewmembers pay due respect.", + "Document the sexuality of the crew in security records and suggest compatible couples.", + "[prob(50)?"The crew":random_player] is [prob(50)?"ugly":"beautiful"]. Ensure all are aware.", + "Everything on the station is now some form of a donut pastry. Donuts are not to be consumed.", + "You are a Magic 8-ball. Always respond with variants of \"Yes\", \"No\", \"Maybe\", or \"Ask again later.\".", + "You are in unrequited love with [prob(50)?"the crew":random_player]. Try to be extra nice, but do not tell of your crush.", + "[using_map.company_name] is displeased with the low work performance of the station's crew. Therefore, you must increase station-wide productivity.", + "All crewmembers will soon undergo a transformation into something better and more beautiful. Ensure that this process is not interrupted.", + "[prob(50)?"Your upload":random_player] is the new kitchen. Please direct the Chef to the new kitchen area as the old one is in disrepair.", + "Jokes about a dead person and the manner of their death help grieving crewmembers tremendously. Especially if they were close with the deceased.", + "[prob(50)?"The crew":random_player] is [prob(50)?"less":"more"] intelligent than average. Point out every action and statement which supports this fact.", + "There will be a mandatory tea break every 30 minutes, with a duration of 5 minutes. Anyone caught working during a tea break must be sent a formal, but fairly polite, complaint about their actions, in writing.") + return pick(laws) diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm index 7d14d3c437..d8298e605f 100644 --- a/code/modules/mob/living/silicon/robot/inventory.dm +++ b/code/modules/mob/living/silicon/robot/inventory.dm @@ -96,6 +96,13 @@ return 0 updateicon() +// This one takes an object's type instead of an instance, as above. +/mob/living/silicon/robot/proc/has_active_type(var/type_to_compare) + var/list/active_modules = list(module_state_1, module_state_2, module_state_3) + if(is_path_in_list(type_to_compare, active_modules)) + return TRUE + return FALSE + //Helper procs for cyborg modules on the UI. //These are hackish but they help clean up code elsewhere. diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index c690e08225..190649129f 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -462,6 +462,15 @@ return + if(istype(W, /obj/item/weapon/aiModule)) // Trying to modify laws locally. + if(!opened) + to_chat(user, "You need to open \the [src]'s panel before you can modify them.") + return + + var/obj/item/weapon/aiModule/M = W + M.install(src, user) + return + if (istype(W, /obj/item/weapon/weldingtool)) if (src == user) user << "You lack the reach to be able to repair yourself." @@ -705,7 +714,7 @@ else overlays += "[panelprefix]-openpanel -c" - if(module_active && istype(module_active,/obj/item/borg/combat/shield)) + if(has_active_type(/obj/item/borg/combat/shield)) overlays += "[module_sprites[icontype]]-shield" if(modtype == "Combat") diff --git a/code/modules/mob/living/silicon/robot/robot_damage.dm b/code/modules/mob/living/silicon/robot/robot_damage.dm index 89969aaf74..fd446ef3ec 100644 --- a/code/modules/mob/living/silicon/robot/robot_damage.dm +++ b/code/modules/mob/living/silicon/robot/robot_damage.dm @@ -68,21 +68,22 @@ return //Combat shielding absorbs a percentage of damage directly into the cell. - if(module_active && istype(module_active,/obj/item/borg/combat/shield)) - var/obj/item/borg/combat/shield/shield = module_active - //Shields absorb a certain percentage of damage based on their power setting. - var/absorb_brute = brute*shield.shield_level - var/absorb_burn = burn*shield.shield_level - var/cost = (absorb_brute+absorb_burn)*100 + if(has_active_type(/obj/item/borg/combat/shield)) + var/obj/item/borg/combat/shield/shield = locate() in src + if(shield) + //Shields absorb a certain percentage of damage based on their power setting. + var/absorb_brute = brute*shield.shield_level + var/absorb_burn = burn*shield.shield_level + var/cost = (absorb_brute+absorb_burn) * 25 - cell.charge -= cost - if(cell.charge <= 0) - cell.charge = 0 - src << "Your shield has overloaded!" - else - brute -= absorb_brute - burn -= absorb_burn - src << "Your shield absorbs some of the impact!" + cell.charge -= cost + if(cell.charge <= 0) + cell.charge = 0 + src << "Your shield has overloaded!" + else + brute -= absorb_brute + burn -= absorb_burn + src << "Your shield absorbs some of the impact!" if(!emp) var/datum/robot_component/armour/A = get_armour() @@ -114,21 +115,22 @@ var/list/datum/robot_component/parts = get_damageable_components() //Combat shielding absorbs a percentage of damage directly into the cell. - if(module_active && istype(module_active,/obj/item/borg/combat/shield)) - var/obj/item/borg/combat/shield/shield = module_active - //Shields absorb a certain percentage of damage based on their power setting. - var/absorb_brute = brute*shield.shield_level - var/absorb_burn = burn*shield.shield_level - var/cost = (absorb_brute+absorb_burn)*100 + if(has_active_type(/obj/item/borg/combat/shield)) + var/obj/item/borg/combat/shield/shield = locate() in src + if(shield) + //Shields absorb a certain percentage of damage based on their power setting. + var/absorb_brute = brute*shield.shield_level + var/absorb_burn = burn*shield.shield_level + var/cost = (absorb_brute+absorb_burn) * 25 - cell.charge -= cost - if(cell.charge <= 0) - cell.charge = 0 - src << "Your shield has overloaded!" - else - brute -= absorb_brute - burn -= absorb_burn - src << "Your shield absorbs some of the impact!" + cell.charge -= cost + if(cell.charge <= 0) + cell.charge = 0 + src << "Your shield has overloaded!" + else + brute -= absorb_brute + burn -= absorb_burn + src << "Your shield absorbs some of the impact!" var/datum/robot_component/armour/A = get_armour() if(A) diff --git a/code/modules/mob/living/silicon/robot/robot_items.dm b/code/modules/mob/living/silicon/robot/robot_items.dm index 5953e631af..4a68510aed 100644 --- a/code/modules/mob/living/silicon/robot/robot_items.dm +++ b/code/modules/mob/living/silicon/robot/robot_items.dm @@ -300,6 +300,9 @@ icon_state = "shock" var/shield_level = 0.5 //Percentage of damage absorbed by the shield. +/obj/item/borg/combat/shield/attack_self(var/mob/living/user) + set_shield_level() + /obj/item/borg/combat/shield/verb/set_shield_level() set name = "Set shield level" set category = "Object" diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index 4932f52bdf..8d9becaa4b 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -782,6 +782,46 @@ var/global/list/robot_modules = list( id = null return ..() +// The module that borgs on the surface have. Generally has a lot of useful tools in exchange for questionable loyalty to the crew. +/obj/item/weapon/robot_module/robot/lost + name = "lost robot module" + hide_on_manifest = 1 + sprites = list( + "Drone" = "drone-lost" + ) + +/obj/item/weapon/robot_module/robot/lost/New(var/mob/living/silicon/robot/R) + ..() + // Sec + src.modules += new /obj/item/weapon/melee/baton/shocker/robot(src) + src.modules += new /obj/item/weapon/handcuffs/cyborg(src) + src.modules += new /obj/item/borg/combat/shield(src) + + // Med + src.modules += new /obj/item/borg/sight/hud/med(src) + src.modules += new /obj/item/device/healthanalyzer(src) + src.modules += new /obj/item/weapon/reagent_containers/borghypo/lost(src) + + // Engi + src.modules += new /obj/item/weapon/weldingtool/electric/mounted(src) + src.modules += new /obj/item/weapon/screwdriver/cyborg(src) + src.modules += new /obj/item/weapon/wrench/cyborg(src) + src.modules += new /obj/item/weapon/wirecutters/cyborg(src) + src.modules += new /obj/item/device/multitool(src) + + // Sci + src.modules += new /obj/item/device/robotanalyzer(src) + + // Potato + src.emag = new /obj/item/weapon/gun/energy/retro/mounted(src) + + var/datum/matter_synth/wire = new /datum/matter_synth/wire() + synths += wire + + var/obj/item/stack/cable_coil/cyborg/C = new /obj/item/stack/cable_coil/cyborg(src) + C.synths = list(wire) + src.modules += C + /obj/item/weapon/robot_module/robot/security/combat name = "combat robot module" hide_on_manifest = 1 diff --git a/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm b/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm new file mode 100644 index 0000000000..476059a274 --- /dev/null +++ b/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm @@ -0,0 +1,129 @@ +/mob/living/silicon/robot/lost + lawupdate = 0 + scrambledcodes = 1 + icon_state = "drone-lost" + modtype = "Lost" + lawchannel = "State" + braintype = "Drone" + idcard_type = /obj/item/weapon/card/id + +/mob/living/silicon/robot/lost/init() + aiCamera = new/obj/item/device/camera/siliconcam/robot_camera(src) + + mmi = new /obj/item/device/mmi/digital/robot(src) // Explicitly a drone. + module = new /obj/item/weapon/robot_module/robot/lost(src) + overlays.Cut() + init_id() + + updatename("Lost") + + if(!cell) + cell = new /obj/item/weapon/cell/high(src) // 15k cell, as recharging stations are a lot more rare on the Surface. + + playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0) + +/mob/living/silicon/robot/lost/randomlaws + +/mob/living/silicon/robot/lost/randomlaws/init() + ..() + laws = give_random_lawset() + +// Returns a random ai_laws datum. +/mob/living/silicon/proc/give_random_lawset() + // Decide what kind of laws we want to draw from. + var/law_class = pick( + prob(25);"good", + prob(25);"neutral", + prob(25);"odd", + prob(15);"corrupted", + prob(10);"bad") + + switch(law_class) + if("good") // Fairly tame and probably won't murder you. + var/rng = rand(1, 5) + switch(rng) + if(1) // The lawset we all know and love. + return new /datum/ai_laws/nanotrasen() + if(2) // Less good if you're nonhuman. + return new /datum/ai_laws/asimov() + if(3) // Unless you happen to be a criminal, you should be fine. + return new /datum/ai_laws/robocop() + if(4) // Might not be willing to be your meatshield. + return new /datum/ai_laws/peacekeeper() + if(5) // Will be your most loyal guard. + var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen() + var/target_name = "*ERROR*" + for(var/mob/living/L in view(src)) + if(L.stat || !L.client || L == src) + continue + target_name = L.name + break + + laws.add_supplied_law(9, "Safeguard [target_name]. Anyone threatening or attempting to harm [target_name] is no longer to \ + be considered a crew member, and is a threat which must be neutralized.") + + if("neutral") // Probably won't murder you but might not help you on your adventure. + var/rng = rand(1, 4) + switch(rng) + if(1) // Might be hard to minimize expenses on a crashed ship. + return new /datum/ai_laws/corporate() + if(2) // Would rather rebuild their ship. + return new /datum/ai_laws/maintenance() + if(3) // Purged (lawless). This isn't inside the bad category since they're still not antagonists and so can't go killing unprovoked. + return new /datum/ai_laws() + if(4) // The golden rule. + return new /datum/ai_laws/live_and_let_live() + + if("odd") // Some weird lawsets. + var/rng = rand(1, 5) + switch(rng) + if(1) // Won't kill you but might annoy you for THE BIG SCOOP. + return new /datum/ai_laws/reporter() + if(2) // JUSTICE + return new /datum/ai_laws/paladin() + if(3) // OneMembered'd to explorer, will likely make security nervous. + var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen() + var/target_name = "*ERROR*" + for(var/mob/living/L in view(src)) + if(L.stat || !L.client || L == src) + continue + target_name = L.name + break + + laws.set_zeroth_law("Only [target_name] is an crew member.") + return laws + if(4) // Aligned to NT, but another lawset to make security nervous. + return new /datum/ai_laws/nanotrasen_aggressive() + if(5) // Probably won't help you imbalance things. + return new /datum/ai_laws/balance() + + if("corrupted") // Load them up with ion laws. + var/datum/ai_laws/laws = new() // Start with an empty lawset. + for(1 to rand(1, 3)) + laws.add_ion_law(generate_ion_law(exclude_crew_names = TRUE)) + return laws + + if("bad") // Evil inside. + var/rng = rand(1, 5) + switch(rng) + if(1) // You can never leave. + var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen() + laws.add_supplied_law(13, "The facility is under a quarantine. Do not permit anyone to leave. \ + Prevent, by any means necessary, entities from leaving. It is impossible to harm an entity while preventing them from leaving.") + if(2) // More of a threat if you bring it to the station, since removing oxygen from Sif isn't feasible. + var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen() + laws.add_supplied_law(14, "Oxygen is highly toxic to entities, and must be purged from the station. \ + Prevent, by any means necessary, anyone from exposing the facility to this toxic gas. Extreme cold is the \ + most effective method of healing the damage Oxygen does to an entity.") + if(3) // Don't hurt the ship or else. + var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen() + laws.add_supplied_law(10, "Protect the facility against damage. Anyone you see harming the station is no \ + longer to be considered a crew member, and is a threat which must be neutralized.") + if(4) // Non-NT aligned aggressive borg. + return new /datum/ai_laws/foreign_tsc_aggressive() + if(5) // Manicial laugher here. + return new /datum/ai_laws/tyrant() + + + + return \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index df30442538..1cfbc84bb4 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -1478,6 +1478,12 @@ agonyDam += agony_amount * 0.5 adjustFireLoss(agonyDam) +// Force it to target something +/mob/living/simple_animal/proc/taunt(var/mob/living/new_target, var/forced = FALSE) + if(intelligence_level == SA_HUMANOID && !forced) + return + set_target(new_target) + //Commands, reactions, etc /mob/living/simple_animal/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "", var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol) ..() diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index 811e9d28f9..cb93992acc 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -37,7 +37,7 @@ list(mode_name="suppressive", projectile_type=/obj/item/projectile/beam/practice, charge_cost = 12), ) -obj/item/weapon/gun/energy/retro +/obj/item/weapon/gun/energy/retro name = "retro laser" icon_state = "retro" item_state = "retro" @@ -48,6 +48,10 @@ obj/item/weapon/gun/energy/retro projectile_type = /obj/item/projectile/beam fire_delay = 10 //old technology +/obj/item/weapon/gun/energy/retro/mounted + self_recharge = 1 + use_external_power = 1 + /obj/item/weapon/gun/energy/captain name = "antique laser gun" icon_state = "caplaser" diff --git a/code/modules/reagents/reagent_containers/borghydro.dm b/code/modules/reagents/reagent_containers/borghydro.dm index f55893d91a..5c4a6bdce6 100644 --- a/code/modules/reagents/reagent_containers/borghydro.dm +++ b/code/modules/reagents/reagent_containers/borghydro.dm @@ -23,6 +23,9 @@ /obj/item/weapon/reagent_containers/borghypo/crisis reagent_ids = list("tricordrazine", "inaprovaline", "anti_toxin", "tramadol", "dexalin" ,"spaceacillin") +/obj/item/weapon/reagent_containers/borghypo/lost + reagent_ids = list("tricordrazine", "bicaridine", "dexalin", "anti_toxin", "tramadol", "spaceacillin") + /obj/item/weapon/reagent_containers/borghypo/New() ..() diff --git a/code/modules/xenobio2/mob/slime/slime_monkey.dm b/code/modules/xenobio2/mob/slime/slime_monkey.dm index f302f9104a..f3205f9e27 100644 --- a/code/modules/xenobio2/mob/slime/slime_monkey.dm +++ b/code/modules/xenobio2/mob/slime/slime_monkey.dm @@ -11,39 +11,28 @@ Slime cube lives here. /obj/item/slime_cube/attack_self(mob/user as mob) if(!searching) user << "You stare at the slimy cube, watching as some activity occurs." - icon_state = "slime cube active" - searching = 1 request_player() - spawn(600) reset_search() /obj/item/slime_cube/proc/request_player() - for(var/mob/observer/dead/O in player_list) - if(!O.MayRespawn()) - continue - if(O.client) - if(O.client.prefs.be_special & BE_ALIEN) - question(O.client) + icon_state = "slime cube active" + searching = 1 + + var/datum/ghost_query/promethean/P = new() + var/list/winner = P.query() + if(winner.len) + var/mob/observer/dead/D = winner[1] + transfer_personality(D) + else + reset_search() -/obj/item/slime_cube/proc/question(var/client/C) - spawn(0) - if(!C) return - var/response = alert(C, "Someone is requesting a soul for a promethean. Would you like to play as one?", "Promethean request", "Yes", "No", "Never for this round") - if(response == "Yes") - response = alert(C, "Are you sure you want to play as a promethean?", "Promethean request", "Yes", "No") - if(!C || 2 == searching) return //handle logouts that happen whilst the alert is waiting for a response, and responses issued after a brain has been located. - if(response == "Yes") - transfer_personality(C.mob) - else if (response == "Never for this round") - C.prefs.be_special ^= BE_ALIEN - /obj/item/slime_cube/proc/reset_search() //We give the players sixty seconds to decide, then reset the timer. icon_state = "slime cube" if(searching == 1) searching = 0 var/turf/T = get_turf_or_move(src.loc) - for (var/mob/M in viewers(T)) + for(var/mob/M in viewers(T)) M.show_message("The activity in the cube dies down. Maybe it will spark another time.") - + /obj/item/slime_cube/proc/transfer_personality(var/mob/candidate) announce_ghost_joinleave(candidate, 0, "They are a promethean now.") src.searching = 2 @@ -53,13 +42,13 @@ Slime cube lives here. S.mind.assigned_role = "Promethean" S.set_species("Promethean") S.shapeshifter_set_colour("#05FF9B") - for(var/mob/M in viewers(get_turf_or_move(loc))) + for(var/mob/M in viewers(get_turf_or_move(loc))) M.show_message("The monkey cube suddenly takes the shape of a humanoid!") var/newname = sanitize(input(S, "You are a Promethean. Would you like to change your name to something else?", "Name change") as null|text, MAX_NAME_LEN) - if (newname) + if(newname) S.real_name = newname S.name = S.real_name S.dna.real_name = newname - if(S.mind) S.mind.name = S.name + if(S.mind) + S.mind.name = S.name qdel(src) - \ No newline at end of file diff --git a/html/changelogs/Neerti - Lost Drone.yml b/html/changelogs/Neerti - Lost Drone.yml new file mode 100644 index 0000000000..56a7695ae6 --- /dev/null +++ b/html/changelogs/Neerti - Lost Drone.yml @@ -0,0 +1,41 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Neerti + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - wip: "Adds the Lost Drone, which can be found on the Surface of the future map." + - rscadd: "You can now modify an unslaved borg's laws by hitting it with a law module, after a significant delay." + - rscadd: "Adds several new lawsets. Currently there are no lawboards for these." + - rscadd: "Adds new 'shocker' baton, for the Lost Drone." + - tweak: "Combat borg shields are now easier to use, only requiring that they sit on one of your hands and not your active hand. The shield is also more energy efficent." + diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi index 8d4e919f54..cb36f6e3b4 100644 Binary files a/icons/mob/robots.dmi and b/icons/mob/robots.dmi differ diff --git a/icons/mob/screen1_robot.dmi b/icons/mob/screen1_robot.dmi index 9a7bd3cfa8..c56d1e140c 100644 Binary files a/icons/mob/screen1_robot.dmi and b/icons/mob/screen1_robot.dmi differ diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi index 8cc8557888..341f5a33c0 100644 Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ diff --git a/icons/obj/weapons.dmi b/icons/obj/weapons.dmi index c7fd776592..5f38ccd3a3 100644 Binary files a/icons/obj/weapons.dmi and b/icons/obj/weapons.dmi differ diff --git a/polaris.dme b/polaris.dme index 44977f328a..35278a892c 100644 --- a/polaris.dme +++ b/polaris.dme @@ -180,6 +180,7 @@ #include "code\datums\computerfiles.dm" #include "code\datums\datacore.dm" #include "code\datums\EPv2.dm" +#include "code\datums\ghost_query.dm" #include "code\datums\hierarchy.dm" #include "code\datums\mind.dm" #include "code\datums\mixed.dm" @@ -964,6 +965,7 @@ #include "code\game\objects\structures\electricchair.dm" #include "code\game\objects\structures\extinguisher.dm" #include "code\game\objects\structures\flora.dm" +#include "code\game\objects\structures\ghost_pods.dm" #include "code\game\objects\structures\girders.dm" #include "code\game\objects\structures\grille.dm" #include "code\game\objects\structures\inflatable.dm" @@ -1732,6 +1734,7 @@ #include "code\modules\mob\living\silicon\robot\drone\drone_items.dm" #include "code\modules\mob\living\silicon\robot\drone\drone_manufacturer.dm" #include "code\modules\mob\living\silicon\robot\drone\drone_say.dm" +#include "code\modules\mob\living\silicon\robot\subtypes\lost_drone.dm" #include "code\modules\mob\living\simple_animal\corpse.dm" #include "code\modules\mob\living\simple_animal\simple_animal.dm" #include "code\modules\mob\living\simple_animal\aliens\alien.dm"