Artur
2020-06-17 17:07:42 +03:00
parent 43951bf44e
commit 7055e6c47a
23 changed files with 300 additions and 5 deletions

View File

@@ -23,6 +23,7 @@ GLOBAL_LIST_EMPTY(antagonists)
var/show_name_in_check_antagonists = FALSE //Will append antagonist name in admin listings - use for categories that share more than one antag type
var/list/blacklisted_quirks = list(/datum/quirk/nonviolent,/datum/quirk/mute) // Quirks that will be removed upon gaining this antag. Pacifist and mute are default.
var/threat = 0 // Amount of threat this antag poses, for dynamic mode
var/show_to_ghosts = FALSE // Should this antagonist be shown as antag to ghosts? Shouldn't be used for stealthy antagonists like traitors
var/list/skill_modifiers

View File

@@ -7,6 +7,7 @@
job_rank = ROLE_ABDUCTOR
show_in_antagpanel = FALSE //should only show subtypes
threat = 5
show_to_ghosts = TRUE
var/datum/team/abductor_team/team
var/sub_role
var/outfit
@@ -32,6 +33,10 @@
show_in_antagpanel = TRUE
skill_modifiers = list(/datum/skill_modifier/job/affinity/surgery)
/datum/antagonist/abductor/scientist/onemanteam
name = "Abductor Solo"
outfit = /datum/outfit/abductor/scientist/onemanteam
/datum/antagonist/abductor/create_team(datum/team/abductor_team/new_team)
if(!new_team)
return

View File

@@ -2,6 +2,7 @@
name = "Blob"
roundend_category = "blobs"
antagpanel_category = "Blob"
show_to_ghosts = TRUE
job_rank = ROLE_BLOB
threat = 20
var/datum/action/innate/blobpop/pop_action

View File

@@ -92,6 +92,7 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
//Don't delete upon mind destruction, otherwise soul re-selling will break.
delete_on_mind_deletion = FALSE
threat = 5
show_to_ghosts = TRUE
var/obligation
var/ban
var/bane

View File

@@ -2,6 +2,7 @@
name = "Sentient Disease"
roundend_category = "diseases"
antagpanel_category = "Disease"
show_to_ghosts = TRUE
var/disease_name = ""
/datum/antagonist/disease/on_gain()

View File

@@ -12,6 +12,7 @@
var/list/name_source
threat = -5
show_in_antagpanel = FALSE
show_to_ghosts = TRUE
antag_moodlet = /datum/mood_event/focused
/datum/antagonist/ert/on_gain()

View File

@@ -9,6 +9,7 @@
roundend_category = "monkeys"
antagpanel_category = "Monkey"
threat = 3
show_to_ghosts = TRUE
var/datum/team/monkey/monkey_team
var/monkey_only = TRUE

View File

@@ -3,3 +3,4 @@
show_in_antagpanel = FALSE
show_name_in_check_antagonists = TRUE
threat = 5
show_to_ghosts = TRUE

View File

@@ -3,6 +3,7 @@
antagpanel_category = "Ninja"
job_rank = ROLE_NINJA
show_name_in_check_antagonists = TRUE
show_to_ghosts = TRUE
antag_moodlet = /datum/mood_event/focused
threat = 8
var/helping_station = FALSE

View File

@@ -6,6 +6,7 @@
antag_moodlet = /datum/mood_event/focused
threat = 10
skill_modifiers = list(/datum/skill_modifier/job/level/wiring)
show_to_ghosts = TRUE
var/datum/team/nuclear/nuke_team
var/always_new_team = FALSE //If not assigned a team by default ops will try to join existing ones, set this to TRUE to always create new team.
var/send_to_spawnpoint = TRUE //Should the user be moved to default spawnpoint.

View File

@@ -4,6 +4,7 @@
show_in_antagpanel = FALSE
var/datum/objective/mission
var/datum/team/ert/ert_team
show_to_ghosts = TRUE
/datum/antagonist/official/greet()
to_chat(owner, "<B><font size=3 color=red>You are a CentCom Official.</font></B>")

View File

@@ -4,6 +4,7 @@
roundend_category = "space pirates"
antagpanel_category = "Pirate"
threat = 5
show_to_ghosts = TRUE
var/datum/team/pirate/crew
/datum/antagonist/pirate/greet()

View File

@@ -3,6 +3,7 @@
show_in_antagpanel = FALSE
show_name_in_check_antagonists = TRUE
threat = 5
show_to_ghosts = TRUE
/datum/antagonist/revenant/greet()
owner.announce_objectives()

View File

@@ -1,6 +1,8 @@
/datum/antagonist/santa
name = "Santa"
show_in_antagpanel = FALSE
show_name_in_check_antagonists = TRUE
show_to_ghosts = TRUE
/datum/antagonist/santa/on_gain()
. = ..()

View File

@@ -6,6 +6,7 @@
threat = 10
job_rank = ROLE_ALIEN
show_in_antagpanel = FALSE
show_to_ghosts = TRUE
/datum/antagonist/slaughter/on_gain()
forge_objectives()

View File

@@ -13,6 +13,7 @@
var/move_to_lair = TRUE
var/outfit_type = /datum/outfit/wizard
var/wiz_age = WIZARD_AGE_MIN /* Wizards by nature cannot be too young. */
show_to_ghosts = TRUE
/datum/antagonist/wizard/on_gain()
register()

View File

@@ -12,6 +12,7 @@
name = "Xenomorph"
job_rank = ROLE_ALIEN
show_in_antagpanel = FALSE
show_to_ghosts = TRUE
var/datum/team/xeno/xeno_team
threat = 3

View File

@@ -369,9 +369,12 @@
"dna_extra.gif" = 'html/dna_extra.gif'
)
/*
/datum/asset/simple/orbit
assets = list(
"ghost.png" = 'html/ghost.png'
)
assets = list(
"ghost.png" = 'html/ghost.png'
)
*/

View File

@@ -54,6 +54,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
// Used for displaying in ghost chat, without changing the actual name
// of the mob
var/deadchat_name
var/datum/orbit_menu/orbit_menu
var/datum/spawners_menu/spawners_menu
/mob/dead/observer/Initialize(mapload, mob/body)
@@ -161,6 +162,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
updateallghostimages()
QDEL_NULL(orbit_menu)
QDEL_NULL(spawners_menu)
return ..()
@@ -490,10 +492,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set name = "Orbit" // "Haunt"
set desc = "Follow and orbit a mob."
var/list/mobs = getpois(skip_mindless=1)
var/input = input("Please, select a mob!", "Haunt", null, null) as null|anything in mobs
var/mob/target = mobs[input]
ManualFollow(target)
if(!orbit_menu)
orbit_menu = new(src)
orbit_menu.ui_interact(src)
// This is the ghost's follow verb with an argument
/mob/dead/observer/proc/ManualFollow(atom/movable/target)

View File

@@ -0,0 +1,78 @@
/datum/orbit_menu
var/mob/dead/observer/owner
/datum/orbit_menu/New(mob/dead/observer/new_owner)
if(!istype(new_owner))
qdel(src)
owner = new_owner
/datum/orbit_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state)
if (!ui)
ui = new(user, src, ui_key, "Orbit", "Orbit", 350, 700, master_ui, state)
ui.open()
/datum/orbit_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if (..())
return
if (action == "orbit")
var/list/pois = getpois(skip_mindless = 1)
var/atom/movable/poi = pois[params["name"]]
if (poi != null)
owner.ManualFollow(poi)
ui.close()
/datum/orbit_menu/ui_data(mob/user)
var/list/data = list()
var/list/alive = list()
var/list/antagonists = list()
var/list/dead = list()
var/list/ghosts = list()
var/list/misc = list()
var/list/npcs = list()
var/list/pois = getpois(skip_mindless = 1)
for (var/name in pois)
var/list/serialized = list()
serialized["name"] = name
var/poi = pois[name]
var/mob/M = poi
if (istype(M))
if (isobserver(M))
ghosts += list(serialized)
else if (M.stat == DEAD)
dead += list(serialized)
else if (M.mind == null)
npcs += list(serialized)
else
var/number_of_orbiters = M.orbiters?.orbiters?.len
if (number_of_orbiters)
serialized["orbiters"] = number_of_orbiters
var/datum/mind/mind = M.mind
var/was_antagonist = FALSE
for (var/_A in mind.antag_datums)
var/datum/antagonist/A = _A
if (A.show_to_ghosts)
was_antagonist = TRUE
serialized["antag"] = A.name
antagonists += list(serialized)
break
if (!was_antagonist)
alive += list(serialized)
else
misc += list(serialized)
data["alive"] = alive
data["antagonists"] = antagonists
data["dead"] = dead
data["ghosts"] = ghosts
data["misc"] = misc
data["npcs"] = npcs
return data

BIN
html/ghost.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -2319,6 +2319,8 @@
#include "code\modules\mob\dead\observer\observer.dm"
#include "code\modules\mob\dead\observer\observer_movement.dm"
#include "code\modules\mob\dead\observer\say.dm"
#include "code\modules\mob\dead\observer\observer_say.dm"
#include "code\modules\mob\dead\observer\orbit.dm"
#include "code\modules\mob\living\blood.dm"
#include "code\modules\mob\living\bloodcrawl.dm"
#include "code\modules\mob\living\damage_procs.dm"

View File

@@ -0,0 +1,188 @@
import { createSearch } from 'common/string';
import { Box, Button, Input, Section } from '../components';
import { Window } from '../layouts';
import { useBackend, useLocalState } from '../backend';
const PATTERN_DESCRIPTOR = / \[(?:ghost|dead)\]$/;
const PATTERN_NUMBER = / \(([0-9]+)\)$/;
const searchFor = searchText => createSearch(searchText, thing => thing.name);
const compareString = (a, b) => a < b ? -1 : a > b;
const compareNumberedText = (a, b) => {
const aName = a.name;
const bName = b.name;
// Check if aName and bName are the same except for a number at the end
// e.g. Medibot (2) and Medibot (3)
const aNumberMatch = aName.match(PATTERN_NUMBER);
const bNumberMatch = bName.match(PATTERN_NUMBER);
if (aNumberMatch
&& bNumberMatch
&& aName.replace(PATTERN_NUMBER, "") === bName.replace(PATTERN_NUMBER, "")
) {
const aNumber = parseInt(aNumberMatch[1], 10);
const bNumber = parseInt(bNumberMatch[1], 10);
return aNumber - bNumber;
}
return compareString(aName, bName);
};
const BasicSection = (props, context) => {
const { act } = useBackend(context);
const { searchText, source, title } = props;
const things = source.filter(searchFor(searchText));
things.sort(compareNumberedText);
return source.length > 0 && (
<Section title={title}>
{things.map(thing => (
<Button
key={thing.name}
content={thing.name.replace(PATTERN_DESCRIPTOR, "")}
onClick={() => act("orbit", {
name: thing.name,
})} />
))}
</Section>
);
};
const OrbitedButton = (props, context) => {
const { act } = useBackend(context);
const { color, thing } = props;
return (
<Button
color={color}
onClick={() => act("orbit", {
name: thing.name,
})}>
{thing.name}
{thing.orbiters && (
<Box inline ml={1}>
{"("}{thing.orbiters}{" "}
<Box
as="img"
src="ghost.png"
opacity={0.7} />
{")"}
</Box>
)}
</Button>
);
};
export const Orbit = (props, context) => {
const { act, data } = useBackend(context);
const {
alive,
antagonists,
dead,
ghosts,
misc,
npcs,
} = data;
const [searchText, setSearchText] = useLocalState(context, "searchText", "");
const collatedAntagonists = {};
for (const antagonist of antagonists) {
if (collatedAntagonists[antagonist.antag] === undefined) {
collatedAntagonists[antagonist.antag] = [];
}
collatedAntagonists[antagonist.antag].push(antagonist);
}
const sortedAntagonists = Object.entries(collatedAntagonists);
sortedAntagonists.sort((a, b) => {
return compareString(a[0], b[0]);
});
const orbitMostRelevant = searchText => {
for (const source of [
sortedAntagonists.map(([_, antags]) => antags),
alive, ghosts, dead, npcs, misc,
]) {
const member = source
.filter(searchFor(searchText))
.sort(compareNumberedText)[0];
if (member !== undefined) {
act("orbit", { name: member.name });
break;
}
}
};
return (
<Window>
<Window.Content>
<Section>
<Input
fluid
value={searchText}
onInput={(_, value) => setSearchText(value)}
onEnter={(_, value) => orbitMostRelevant(value)} />
</Section>
{antagonists.length > 0 && (
<Section title="Ghost-Visible Antagonists">
{sortedAntagonists.map(([name, antags]) => (
<Section key={name} title={name} level={2}>
{antags
.filter(searchFor(searchText))
.sort(compareNumberedText)
.map(antag => (
<OrbitedButton
key={antag.name}
color="bad"
thing={antag}
/>
))}
</Section>
))}
</Section>
)}
<Section title="Alive">
{alive
.filter(searchFor(searchText))
.sort(compareNumberedText)
.map(thing => (
<OrbitedButton
key={thing.name}
color="good"
thing={thing} />
))}
</Section>
<BasicSection
title="Ghosts"
source={ghosts}
searchText={searchText}
/>
<BasicSection
title="Dead"
source={dead}
searchText={searchText}
/>
<BasicSection
title="NPCs"
source={npcs}
searchText={searchText}
/>
<BasicSection
title="Misc"
source={misc}
searchText={searchText}
/>
</Window.Content>
</Window>
);
};