mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-27 17:41:50 +00:00
## About The Pull Request Melee attack chain now has a list passed along with it, `attack_modifiers`, which you can stick force modifiers to change the resulting attack This is basically a soft implementation of damage packets until a more definitive pr, but one that only applies to item attack chain, and not unarmed attacks. This change was done to facilitate a baton refactor - batons no longer hack together their own attack chain, and are now integrated straight into the real attack chain. This refactor itself was done because batons don't send any attack signals, which has been annoying in the past (for swing combat). ## Changelog 🆑 Melbert refactor: Batons have been refactored again. Baton stuns now properly count as an attack, when before it was a nothing. Report any oddities, particularly in regards to harmbatonning vs normal batonning. refactor: The method of adjusting item damage mid-attack has been refactored - some affected items include the Nullblade and knives. Report any strange happenings with damage numbers. refactor: A few objects have been moved to the new interaction chain - records consoles, mawed crucible, alien weeds and space vines, hedges, restaurant portals, and some mobs - to name a few. fix: Spears only deal bonus damage against secure lockers, not all closet types (including crates) /🆑
487 lines
16 KiB
Plaintext
487 lines
16 KiB
Plaintext
/// How many lines of log we keep
|
|
#define EXODRONE_LOG_SIZE 15
|
|
/// Size of drone storage shared between loot and tools.
|
|
#define EXODRONE_CARGO_SLOTS 6
|
|
|
|
// Fuel types and travel time per unit of distance on that fuel.
|
|
#define FUEL_BASIC "basic"
|
|
#define BASIC_FUEL_TIME_COST 250
|
|
|
|
#define FUEL_ADVANCED "advanced"
|
|
#define ADVANCED_FUEL_TIME_COST 200
|
|
|
|
#define FUEL_EXOTIC "exotic"
|
|
#define EXOTIC_FUEL_TIME_COST 100
|
|
|
|
/// All exodrones.
|
|
GLOBAL_LIST_EMPTY(exodrones)
|
|
/// All exodrone launchers.
|
|
GLOBAL_LIST_EMPTY(exodrone_launchers)
|
|
|
|
/// Exploration drone
|
|
/obj/item/exodrone
|
|
name = "exploration drone"
|
|
desc = "A long range, semi-autonomous exploration drone."
|
|
icon = 'icons/obj/exploration.dmi'
|
|
icon_state = "drone"
|
|
w_class = WEIGHT_CLASS_BULKY
|
|
|
|
/// Current drone status, see code\__DEFINES\adventure.dm
|
|
var/drone_status = EXODRONE_IDLE
|
|
/// Are we currently controlled by remote terminal, blocks other terminals from interacting with this drone.
|
|
var/controlled = FALSE
|
|
/// Site we're currently at, null means station.
|
|
var/datum/exploration_site/location
|
|
/// Site we're currently travelling to, null means going back to station - check drone status if you want to check if traveling or idle
|
|
var/datum/exploration_site/travel_target
|
|
/// Total travel time to our current target
|
|
var/travel_time
|
|
/// Id of travel timer
|
|
var/travel_timer_id
|
|
/// Message that will show up on busy screen
|
|
var/busy_message = "Doing something..."
|
|
/// When we entered busy state
|
|
var/busy_start_time
|
|
/// How long will busy state last
|
|
var/busy_duration
|
|
// Our current adventure if any.
|
|
var/datum/adventure/current_adventure
|
|
// Our current simple event ui data if any
|
|
var/list/current_event_ui_data
|
|
/// Pad we've launched from, we'll try to land on this one first when coming back if it still exists.
|
|
var/datum/weakref/last_pad
|
|
/// Log of recent events
|
|
var/list/drone_log = list()
|
|
/// List of tools, EXODRONE_TOOL_WELDER etc
|
|
var/list/tools = list()
|
|
// Current travel cost per 1 distance in deciseconds
|
|
var/travel_cost_coeff = BASIC_FUEL_TIME_COST
|
|
/// Repeated drone name counter
|
|
var/static/name_counter = list()
|
|
/// Used to provide source to the regex replacement function. DO NOT MODIFY DIRECTLY
|
|
var/static/obj/item/exodrone/_regex_context
|
|
|
|
/obj/item/exodrone/Initialize(mapload)
|
|
. = ..()
|
|
if(name == /obj/item/exodrone::name)
|
|
name = pick(strings(EXODRONE_FILE,"probe_names"))
|
|
if(name_counter[name])
|
|
name_counter[name]++
|
|
name = "[name] \Roman[name_counter[name]]"
|
|
else
|
|
name_counter[name] = 1
|
|
else
|
|
name = name
|
|
GLOB.exodrones += src
|
|
// Cargo storage
|
|
create_storage(max_slots = EXODRONE_CARGO_SLOTS, canthold = GLOB.blacklisted_cargo_types)
|
|
|
|
/obj/item/exodrone/Destroy()
|
|
. = ..()
|
|
GLOB.exodrones -= src
|
|
|
|
/// Description for drone listing, describes location and current status
|
|
/obj/item/exodrone/proc/ui_description()
|
|
switch(drone_status)
|
|
if(EXODRONE_TRAVEL)
|
|
return travel_target ? "Traveling to [travel_target.display_name()]." : "Traveling back to station."
|
|
if(EXODRONE_EXPLORATION, EXODRONE_ADVENTURE, EXODRONE_BUSY)
|
|
return "Exploring [location?.display_name() || "ERROR"]." // better safe than sorry.
|
|
if(EXODRONE_IDLE)
|
|
return "Idle."
|
|
|
|
// Searches for blacklisted items hidden inside containers
|
|
/obj/item/exodrone/proc/check_blacklist()
|
|
for(var/obj/item/contained_item in contents)
|
|
if(contained_item.contents)
|
|
for(var/subcontained_object in contained_item.get_all_contents())
|
|
if(is_type_in_typecache(subcontained_object, GLOB.blacklisted_cargo_types))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/// Starts travel for site, does not validate if it's possible
|
|
/obj/item/exodrone/proc/launch_for(datum/exploration_site/target_site)
|
|
if(!location) //We're launching from station, fuel up
|
|
var/obj/machinery/exodrone_launcher/pad = locate() in loc
|
|
pad.fuel_up(src)
|
|
pad.launch_effect()
|
|
last_pad = WEAKREF(pad)
|
|
drone_log("Launched from [pad.name] and set course for [target_site.display_name()]")
|
|
else
|
|
drone_log("Launched from [location.display_name()] and set course for [target_site ? target_site.display_name() : station_name()]")
|
|
set_status(EXODRONE_TRAVEL)
|
|
moveToNullspace()
|
|
var/distance_to_travel = target_site ? target_site.distance : location.distance //If we're going home distance is distance of our current location
|
|
if(location && target_site) //Traveling site to site is faster (don't think too hard on 3d space logistics here)
|
|
distance_to_travel = max(abs(target_site.distance - location.distance),1)
|
|
travel_target = target_site
|
|
travel_time = travel_cost_coeff*distance_to_travel
|
|
travel_timer_id = addtimer(CALLBACK(src, PROC_REF(finish_travel)),travel_time,TIMER_STOPPABLE)
|
|
|
|
/// Travel cleanup
|
|
/obj/item/exodrone/proc/finish_travel()
|
|
location = travel_target
|
|
travel_timer_id = null
|
|
travel_time = null
|
|
if(location)//We're arriving at exploration site
|
|
location.on_drone_arrival(src)
|
|
set_status(EXODRONE_EXPLORATION)
|
|
else
|
|
var/obj/machinery/exodrone_launcher = find_landing_pad()
|
|
if(exodrone_launcher)
|
|
forceMove(get_turf(exodrone_launcher))
|
|
drone_log("Arrived at [station_name()]. Landing at [exodrone_launcher].")
|
|
else
|
|
var/turf/drop_zone = drop_somewhere_on_station()
|
|
drone_log("Arrived at [station_name()]. Emergency landing at [drop_zone.loc.name].")
|
|
set_status(EXODRONE_IDLE)
|
|
|
|
/obj/item/exodrone/proc/set_status(new_status)
|
|
SEND_SIGNAL(src,COMSIG_EXODRONE_STATUS_CHANGED)
|
|
drone_status = new_status
|
|
|
|
/// Cargo space left
|
|
/obj/item/exodrone/proc/space_left()
|
|
return EXODRONE_CARGO_SLOTS - length(contents) - length(tools)
|
|
|
|
/// Adds drone tool and resizes storage.
|
|
/obj/item/exodrone/proc/add_tool(tool_type)
|
|
if(space_left() > 0 && (tool_type in GLOB.exodrone_tool_metadata))
|
|
tools += tool_type
|
|
update_storage_size()
|
|
|
|
/// Removes drone tool and resizes storage.
|
|
/obj/item/exodrone/proc/remove_tool(tool_type)
|
|
tools -= tool_type
|
|
update_storage_size()
|
|
|
|
/// Resizes storage component depending on slots used by tools.
|
|
/obj/item/exodrone/proc/update_storage_size()
|
|
atom_storage.max_slots = EXODRONE_CARGO_SLOTS - length(tools)
|
|
|
|
/// Builds ui data for drone storage.
|
|
/obj/item/exodrone/proc/get_cargo_data()
|
|
. = list()
|
|
for(var/tool in tools)
|
|
. += list(list("type"="tool","name"=tool))
|
|
for(var/obj/cargo in contents)
|
|
. += list(list("type"="cargo","name"=cargo.name, "ref"=ref(cargo)))
|
|
for(var/_ in 1 to space_left())
|
|
. += list(list("type"="empty","name"="Free space"))
|
|
|
|
/// Tries to add loot to drone cargo while respecting space left
|
|
/obj/item/exodrone/proc/try_transfer(obj/loot, delete_on_failure=TRUE)
|
|
if(space_left())
|
|
loot.forceMove(src)
|
|
drone_log("Acquired [loot.name].")
|
|
else
|
|
drone_log("Abandoned [loot.name] due to lack of space.")
|
|
if(delete_on_failure)
|
|
qdel(loot)
|
|
|
|
/// Crashes the drone somewhere random if there's no launchpad to be found.
|
|
/obj/item/exodrone/proc/drop_somewhere_on_station()
|
|
var/turf/random_spot = get_safe_random_station_turf_equal_weight()
|
|
|
|
var/obj/structure/closet/supplypod/pod = podspawn(list(
|
|
"target" = random_spot,
|
|
))
|
|
forceMove(pod)
|
|
return random_spot
|
|
|
|
/// Tries to find landing pad, starting with the one we launched from.
|
|
/obj/item/exodrone/proc/find_landing_pad()
|
|
var/obj/machinery/exodrone_launcher/landing_pad = last_pad?.resolve()
|
|
if(landing_pad)
|
|
return landing_pad
|
|
for(var/obj/machinery/exodrone_launcher/other_pad in GLOB.exodrone_launchers)
|
|
return other_pad
|
|
|
|
/// encounters random or specificed event for the current site.
|
|
/obj/item/exodrone/proc/explore_site(datum/exploration_event/specific_event)
|
|
if(!specific_event) //encounter random event
|
|
var/list/events_to_encounter = list()
|
|
for(var/datum/exploration_event/event in location.events)
|
|
if(event.visited)
|
|
continue
|
|
events_to_encounter += event
|
|
if(!length(events_to_encounter))
|
|
drone_log("It seems there's nothing interesting left around [location.name].")
|
|
return
|
|
var/datum/exploration_event/encountered_event = pick(events_to_encounter)
|
|
encountered_event.encounter(src)
|
|
else if(specific_event.is_targetable())
|
|
specific_event.encounter(src)
|
|
|
|
/obj/item/exodrone/proc/get_adventure_data()
|
|
var/list/data = current_adventure?.ui_data()
|
|
data["description"] = updateKeywords(data["description"])
|
|
var/list/choices = data["choices"]
|
|
for(var/list/choice in choices)
|
|
choice["text"] = updateKeywords(choice["text"])
|
|
return data
|
|
|
|
///Replaces $$SITE_NAME with site name and $$QualityName with quality values
|
|
/obj/item/exodrone/proc/updateKeywords(text)
|
|
_regex_context = src
|
|
var/static/regex/keywordRegex = regex(@"\$\$(\S*)","g")
|
|
. = keywordRegex.Replace(text, /obj/item/exodrone/proc/replace_keyword)
|
|
_regex_context = null
|
|
|
|
/// This is called with src = regex datum, so don't try to access any instance variables directly here.
|
|
/obj/item/exodrone/proc/replace_keyword(match,g1)
|
|
REGEX_REPLACE_HANDLER
|
|
switch(g1)
|
|
if("SITE_NAME")
|
|
return _regex_context.location.display_name()
|
|
else
|
|
if(_regex_context.current_adventure.qualities[g1])
|
|
return "[_regex_context.current_adventure.qualities[g1]]"
|
|
else
|
|
return ""
|
|
|
|
/obj/item/exodrone/proc/start_adventure(datum/adventure/adventure)
|
|
current_adventure = adventure
|
|
RegisterSignal(current_adventure,COMSIG_ADVENTURE_FINISHED, PROC_REF(resolve_adventure))
|
|
RegisterSignal(current_adventure,COMSIG_ADVENTURE_QUALITY_INIT, PROC_REF(add_tool_qualities))
|
|
RegisterSignal(current_adventure,COMSIG_ADVENTURE_DELAY_START, PROC_REF(adventure_delay_start))
|
|
RegisterSignal(current_adventure,COMSIG_ADVENTURE_DELAY_END, PROC_REF(adventure_delay_end))
|
|
set_status(EXODRONE_ADVENTURE)
|
|
current_adventure.start_adventure()
|
|
|
|
/// Handles finishing adventure
|
|
/obj/item/exodrone/proc/resolve_adventure(datum/source,result)
|
|
SIGNAL_HANDLER
|
|
switch(result)
|
|
if(ADVENTURE_RESULT_SUCCESS)
|
|
award_adventure_loot()
|
|
UnregisterSignal(current_adventure,list(COMSIG_ADVENTURE_FINISHED,COMSIG_ADVENTURE_QUALITY_INIT,COMSIG_ADVENTURE_DELAY_START,COMSIG_ADVENTURE_DELAY_END))
|
|
current_adventure = null
|
|
set_status(EXODRONE_EXPLORATION)
|
|
return
|
|
if(ADVENTURE_RESULT_DAMAGE)
|
|
damage(max_integrity*0.5) //Half health lost
|
|
if(!QDELETED(src)) // Don't bother if we just blown up from the damage
|
|
UnregisterSignal(current_adventure,list(COMSIG_ADVENTURE_FINISHED,COMSIG_ADVENTURE_QUALITY_INIT,COMSIG_ADVENTURE_DELAY_START,COMSIG_ADVENTURE_DELAY_END))
|
|
current_adventure = null
|
|
set_status(EXODRONE_EXPLORATION)
|
|
return
|
|
if(ADVENTURE_RESULT_DEATH)
|
|
qdel(src)
|
|
|
|
/// Adds loot from current adventure to the drone
|
|
/obj/item/exodrone/proc/award_adventure_loot()
|
|
if(length(current_adventure.loot_categories))
|
|
var/generator_type = GLOB.adventure_loot_generator_index[pick(current_adventure.loot_categories)]
|
|
if(!generator_type)
|
|
return //Could probably warn but i suppose this is up to adventure creator.
|
|
var/datum/adventure_loot_generator/generator = new generator_type
|
|
generator.transfer_loot(src)
|
|
|
|
/// Applies adventure qualities based on our tools
|
|
/obj/item/exodrone/proc/add_tool_qualities(datum/source,list/quality_list)
|
|
SIGNAL_HANDLER
|
|
for(var/tool in tools)
|
|
quality_list[tool] = 1
|
|
|
|
/obj/item/exodrone/proc/adventure_delay_start(datum/source, delay_time,delay_message)
|
|
SIGNAL_HANDLER
|
|
set_busy(delay_message,delay_time)
|
|
|
|
/obj/item/exodrone/proc/adventure_delay_end(datum/source)
|
|
SIGNAL_HANDLER
|
|
unset_busy(EXODRONE_ADVENTURE)
|
|
|
|
/// Enters busy mode for a given duration.
|
|
/obj/item/exodrone/proc/set_busy(message,duration)
|
|
if(message)
|
|
busy_message = message
|
|
busy_start_time = world.time
|
|
busy_duration = duration
|
|
set_status(EXODRONE_BUSY)
|
|
|
|
/// Resets busy status
|
|
/obj/item/exodrone/proc/unset_busy(new_status)
|
|
busy_message = initial(busy_message)
|
|
busy_start_time = null
|
|
busy_duration = null
|
|
set_status(new_status)
|
|
|
|
/obj/item/exodrone/proc/busy_time_left()
|
|
return busy_duration - (world.time - busy_start_time)
|
|
|
|
/// Returns failure message or FALSE if we're ready to travel
|
|
/obj/item/exodrone/proc/travel_error()
|
|
/// We're home and on ready pad or exploring and out of any events/adventures
|
|
switch(drone_status)
|
|
if(EXODRONE_IDLE)
|
|
var/obj/machinery/exodrone_launcher/pad = locate() in loc
|
|
if(!pad)
|
|
return "No launcher"
|
|
if(!pad.fuel_canister)
|
|
return "No fuel in launcher"
|
|
if(pad.fuel_canister.uses <= 0)
|
|
return "Launcher fuel used up"
|
|
return FALSE
|
|
if(EXODRONE_EXPLORATION)
|
|
if(current_event_ui_data)
|
|
return "Busy"
|
|
return FALSE
|
|
else
|
|
return ""
|
|
|
|
/// Deals damage in adventures/events.
|
|
/obj/item/exodrone/proc/damage(amount)
|
|
take_damage(amount)
|
|
drone_log("Sustained [amount] damage.")
|
|
|
|
/obj/item/exodrone/proc/drone_log(message)
|
|
if(length(drone_log) > EXODRONE_LOG_SIZE)
|
|
drone_log = list()
|
|
drone_log.Insert(1,message)
|
|
|
|
/obj/item/exodrone/proc/has_tool(tool_type)
|
|
return tools.Find(tool_type)
|
|
|
|
/// Exploration drone launcher
|
|
/obj/machinery/exodrone_launcher
|
|
name = "exploration drone launcher"
|
|
desc = "A launch pad designed to send exploration drones into the great beyond."
|
|
icon = 'icons/obj/exploration.dmi'
|
|
icon_state = "launcher"
|
|
/// Loaded fuel pellet.
|
|
var/obj/item/fuel_pellet/fuel_canister
|
|
|
|
/obj/machinery/exodrone_launcher/Initialize(mapload)
|
|
. = ..()
|
|
GLOB.exodrone_launchers += src
|
|
|
|
/obj/machinery/exodrone_launcher/examine(user)
|
|
. = ..()
|
|
if(fuel_canister)
|
|
. += span_notice("You can remove the [fuel_canister] with a <b>prying tool</b>.")
|
|
|
|
/obj/machinery/exodrone_launcher/attackby(obj/item/weapon, mob/living/user, list/modifiers, list/attack_modifiers)
|
|
if(istype(weapon, /obj/item/fuel_pellet))
|
|
if(fuel_canister)
|
|
to_chat(user, span_warning("There's already fuel loaded inside [src]!"))
|
|
return TRUE
|
|
if(!user.transferItemToLoc(weapon, src))
|
|
return
|
|
fuel_canister = weapon
|
|
update_icon()
|
|
return TRUE
|
|
|
|
if(istype(weapon, /obj/item/exodrone) && user.transferItemToLoc(weapon, drop_location()))
|
|
return TRUE
|
|
|
|
return ..()
|
|
|
|
/obj/machinery/exodrone_launcher/crowbar_act(mob/living/user, obj/item/crowbar)
|
|
if(!fuel_canister)
|
|
return
|
|
|
|
to_chat(user, span_notice("You remove [fuel_canister] from [src]."))
|
|
fuel_canister.forceMove(drop_location())
|
|
fuel_canister = null
|
|
update_icon()
|
|
return TRUE
|
|
|
|
/obj/machinery/exodrone_launcher/Destroy()
|
|
GLOB.exodrone_launchers -= src
|
|
QDEL_NULL(fuel_canister)
|
|
return ..()
|
|
|
|
/obj/machinery/exodrone_launcher/update_overlays()
|
|
. = ..()
|
|
if(fuel_canister && fuel_canister.uses > 0)
|
|
switch(fuel_canister.fuel_type)
|
|
if(FUEL_BASIC)
|
|
. += "launchpad_fuel_basic"
|
|
if(FUEL_ADVANCED)
|
|
. += "launchpad_fuel_advanced"
|
|
if(FUEL_EXOTIC)
|
|
. += "launchpad_fuel_exotic"
|
|
|
|
/*
|
|
* Gets the fuel travel coefficient for what type of fuel is within the launcher.
|
|
*/
|
|
/obj/machinery/exodrone_launcher/proc/get_fuel_coefficent()
|
|
if(!fuel_canister)
|
|
return
|
|
switch(fuel_canister.fuel_type)
|
|
if(FUEL_BASIC)
|
|
return BASIC_FUEL_TIME_COST
|
|
if(FUEL_ADVANCED)
|
|
return ADVANCED_FUEL_TIME_COST
|
|
if(FUEL_EXOTIC)
|
|
return EXOTIC_FUEL_TIME_COST
|
|
|
|
/*
|
|
* Use up some of the fuel within the launcher to power the drone.
|
|
*
|
|
* drone - the drone that's being fuelled by our launcher.
|
|
*/
|
|
/obj/machinery/exodrone_launcher/proc/fuel_up(obj/item/exodrone/drone)
|
|
drone.travel_cost_coeff = get_fuel_coefficent()
|
|
fuel_canister.use()
|
|
update_icon()
|
|
|
|
/*
|
|
* Plays an effect on the pad, with a sound effect to boot.
|
|
*/
|
|
/obj/machinery/exodrone_launcher/proc/launch_effect()
|
|
playsound(src,'sound/effects/podwoosh.ogg',50, FALSE)
|
|
do_smoke(1, holder = src, location = get_turf(src))
|
|
|
|
/obj/machinery/exodrone_launcher/Exited(atom/movable/gone, direction)
|
|
. = ..()
|
|
if(gone == fuel_canister)
|
|
fuel_canister = null
|
|
update_icon()
|
|
|
|
/obj/item/exodrone/proc/get_travel_coeff()
|
|
switch(drone_status)
|
|
if(EXODRONE_IDLE)
|
|
var/obj/machinery/exodrone_launcher/pad = locate() in loc
|
|
if(pad && pad.fuel_canister)
|
|
return pad.get_fuel_coefficent()
|
|
else
|
|
return travel_cost_coeff
|
|
else
|
|
return travel_cost_coeff
|
|
|
|
/obj/item/fuel_pellet
|
|
name = "standard fuel pellet"
|
|
desc = "A compressed fuel pellet for long-distance drone flight."
|
|
icon = 'icons/obj/exploration.dmi'
|
|
icon_state = "fuel_basic"
|
|
/// The type of fuel this pellet has within.
|
|
var/fuel_type = FUEL_BASIC
|
|
/// The amount of uses left in this fuel pellet.
|
|
var/uses = 5
|
|
|
|
/obj/item/fuel_pellet/use(used)
|
|
uses--
|
|
if(uses <= 0)
|
|
qdel(src)
|
|
|
|
/obj/item/fuel_pellet/advanced
|
|
name = "advanced fuel pellet"
|
|
fuel_type = FUEL_ADVANCED
|
|
icon_state = "fuel_advanced"
|
|
|
|
/obj/item/fuel_pellet/exotic
|
|
name = "exotic fuel pellet"
|
|
fuel_type = FUEL_EXOTIC
|
|
icon_state = "fuel_exotic"
|
|
|
|
#undef EXODRONE_LOG_SIZE
|
|
#undef EXODRONE_CARGO_SLOTS
|
|
#undef FUEL_BASIC
|
|
#undef BASIC_FUEL_TIME_COST
|
|
#undef FUEL_ADVANCED
|
|
#undef ADVANCED_FUEL_TIME_COST
|
|
#undef FUEL_EXOTIC
|
|
#undef EXOTIC_FUEL_TIME_COST
|