Files
Bubberstation/code/datums/components/chasm.dm
Ghom 10c2b7364e The fishening v3: Fishing lures. (#86007)
## About The Pull Request
Over half of the line changes are merely from splitting the
fish_types.dm into several files since it was over 1k lines already.

One of the small issues with fishing right now is RNG. You want to get
some specific fish, and you go through all the micromanaging with hooks,
reels and baits only for the random number god to say "nope", and that's
only going to get worse the more fish are in the game.

However, I've a solution: (unconsumable/reusable) fishing lures, each of
which attracts different fish based on different conditions. The only
caveat is that they require to be spun at set intervals (usually 1 to 3
seconds, depending on the lure, with a second-long window). Worry not,
there're visual cues in the form of a green/red light hovering the
fishing float, so you won't get screwed up by the server slowing down or
whatever.
The whole box of lures (12 so far) can be from cargo for the fair price
of 450 credits.

I've also added 5 new fish: monkfish, plaice, pike, another punnier
variant of the pike, perch and squid. The latter is quite special
because of the ink production trait, which lets players use it to blind
others at a close range and when butchered, it yields an ink sac, which
can be processed into a can of squid ink (one less item exclusive to the
produce console), or thrown at people in a sort-of-similar fashion of
banana cream pies (except it's ink).

<details>
  <summary>Images</summary>

Fishing lures (forgot to take my cursor off the veggie one before the
screenshot):

![immagine](https://github.com/user-attachments/assets/8ba7a0f2-2a9f-4177-9c0d-ebeabd8a0ef7)

The five new fish:

![immagine](https://github.com/user-attachments/assets/1c251079-3b39-48bb-af6c-0a35623953a7)

</details>

<details>
<summary>A table of fish catchable wth each lure (excluding
holodeck)</summary>


![table](https://github.com/user-attachments/assets/dee95855-405b-4945-bfc2-70e816e46109)

</details>

A few more things in the CL, baitfish are a thing now.

## Why It's Good For The Game
There should be ways to contrast some of the RNG fishing has. After all,
it's only going to get more random the more fish are in the game.
Furthermore, I find it disappointing that a lot of food stuff is
exclusive to the ingredients console and there're no other ways to get
it.

## Changelog

🆑
add: Added fishing lures to the game. They don't get used up like baits
and let you catch specific kinds of fish, though they need to be spun
every few seconds. The whole set can be ordered from cargo for 450
credits.
balance: The magnet hook now removes dud chances.
add: Added five new fish types: perch, two types of pike, monkfish,
plaice and squid. Squids have a fairly special ink production trait,
which lets you use them (unless dead) to ink people face at close range,
and can be butchered for an ink sac, which can either be processed into
canned squid ink, or thrown at someone.
fix: Refactored throwing a little. Some items (specifically
components/elements) won't be triggered when caught. no more plates
shattering despite being caught for example.
add: Goldfish, lavaloops, needlefish and armorfish can now be used as
baits.
/🆑
2024-09-06 19:50:28 -04:00

324 lines
12 KiB
Plaintext

// Used by /turf/open/chasm and subtypes to implement the "dropping" mechanic
/datum/component/chasm
var/turf/target_turf
var/obj/effect/abstract/chasm_storage/storage
var/fall_message = "GAH! Ah... where are you?"
var/oblivion_message = "You stumble and stare into the abyss before you. It stares back, and you fall into the enveloping dark."
/// List of refs to falling objects -> how many levels deep we've fallen
var/static/list/falling_atoms = list()
var/static/list/forbidden_types = typecacheof(list(
/obj/docking_port,
/obj/effect/abstract,
/obj/effect/collapse,
/obj/effect/constructing_effect,
/obj/effect/dummy/phased_mob,
/obj/effect/ebeam,
/obj/effect/fishing_float,
/obj/effect/hotspot,
/obj/effect/landmark,
/obj/effect/light_emitter/tendril,
/obj/effect/mapping_helpers,
/obj/effect/particle_effect/ion_trails,
/obj/effect/particle_effect/sparks,
/obj/effect/portal,
/obj/effect/projectile,
/obj/effect/spectre_of_resurrection,
/obj/effect/temp_visual,
/obj/effect/wisp,
/obj/energy_ball,
/obj/narsie,
/obj/projectile,
/obj/singularity,
/obj/structure/lattice,
/obj/structure/stone_tile,
/obj/structure/ore_vent,
))
/datum/component/chasm/Initialize(turf/target, mapload)
if(!isturf(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, SIGNAL_ADDTRAIT(TRAIT_CHASM_STOPPED), PROC_REF(on_chasm_stopped))
RegisterSignal(parent, SIGNAL_REMOVETRAIT(TRAIT_CHASM_STOPPED), PROC_REF(on_chasm_no_longer_stopped))
target_turf = target
RegisterSignal(parent, COMSIG_ATOM_ABSTRACT_ENTERED, PROC_REF(entered))
RegisterSignal(parent, COMSIG_ATOM_ABSTRACT_EXITED, PROC_REF(exited))
RegisterSignal(parent, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(initialized_on))
RegisterSignal(parent, COMSIG_ATOM_INTERCEPT_TELEPORTING, PROC_REF(block_teleport))
//allow catwalks to give the turf the CHASM_STOPPED trait before dropping stuff when the turf is changed.
//otherwise don't do anything because turfs and areas are initialized before movables.
if(!mapload)
addtimer(CALLBACK(src, PROC_REF(drop_stuff)), 0)
parent.AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/chasm)
/datum/component/chasm/UnregisterFromParent()
storage = null
/datum/component/chasm/proc/entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER
drop_stuff()
/datum/component/chasm/proc/exited(datum/source, atom/movable/exited)
SIGNAL_HANDLER
UnregisterSignal(exited, list(COMSIG_MOVETYPE_FLAG_DISABLED, COMSIG_LIVING_SET_BUCKLED, COMSIG_MOVABLE_THROW_LANDED))
/datum/component/chasm/proc/initialized_on(datum/source, atom/movable/movable, mapload)
SIGNAL_HANDLER
drop_stuff(movable)
/datum/component/chasm/proc/block_teleport()
return COMPONENT_BLOCK_TELEPORT
/datum/component/chasm/proc/on_chasm_stopped(datum/source)
SIGNAL_HANDLER
UnregisterSignal(source, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON))
for(var/atom/movable/movable as anything in source)
UnregisterSignal(movable, list(COMSIG_MOVETYPE_FLAG_DISABLED, COMSIG_LIVING_SET_BUCKLED, COMSIG_MOVABLE_THROW_LANDED))
/datum/component/chasm/proc/on_chasm_no_longer_stopped(datum/source)
SIGNAL_HANDLER
RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(entered))
RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(exited))
RegisterSignal(parent, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(initialized_on))
drop_stuff()
#define CHASM_NOT_DROPPING 0
#define CHASM_DROPPING 1
///Doesn't drop the movable, but registers a few signals to try again if the conditions change.
#define CHASM_REGISTER_SIGNALS 2
/datum/component/chasm/proc/drop_stuff(atom/movable/dropped_thing)
if(HAS_TRAIT(parent, TRAIT_CHASM_STOPPED))
return
var/atom/atom_parent = parent
var/to_check = dropped_thing ? list(dropped_thing) : atom_parent.contents
for (var/atom/movable/thing as anything in to_check)
var/dropping = droppable(thing)
switch(dropping)
if(CHASM_DROPPING)
INVOKE_ASYNC(src, PROC_REF(drop), thing)
if(CHASM_REGISTER_SIGNALS)
RegisterSignals(thing, list(COMSIG_MOVETYPE_FLAG_DISABLED, COMSIG_LIVING_SET_BUCKLED, COMSIG_MOVABLE_THROW_LANDED), PROC_REF(drop_stuff), TRUE)
/datum/component/chasm/proc/droppable(atom/movable/dropped_thing)
var/datum/weakref/falling_ref = WEAKREF(dropped_thing)
// avoid an infinite loop, but allow falling a large distance
if(falling_atoms[falling_ref] && falling_atoms[falling_ref] > 30)
return CHASM_NOT_DROPPING
if(is_type_in_typecache(dropped_thing, forbidden_types) || (!isliving(dropped_thing) && !isobj(dropped_thing)))
return CHASM_NOT_DROPPING
if(dropped_thing.throwing || (dropped_thing.movement_type & MOVETYPES_NOT_TOUCHING_GROUND))
return CHASM_REGISTER_SIGNALS
for(var/atom/thing_to_check as anything in parent)
if(HAS_TRAIT(thing_to_check, TRAIT_CHASM_STOPPER))
return CHASM_NOT_DROPPING
//Flies right over the chasm
if(ismob(dropped_thing))
var/mob/M = dropped_thing
if(M.buckled) //middle statement to prevent infinite loops just in case!
var/mob/buckled_to = M.buckled
if((!ismob(M.buckled) || (buckled_to.buckled != M)) && !droppable(M.buckled))
return CHASM_REGISTER_SIGNALS
if(ishuman(dropped_thing))
var/mob/living/carbon/human/victim = dropped_thing
if(istype(victim.belt, /obj/item/wormhole_jaunter))
var/obj/item/wormhole_jaunter/jaunter = victim.belt
var/turf/chasm = get_turf(victim)
var/fall_into_chasm = jaunter.chasm_react(victim)
if(!fall_into_chasm)
chasm.visible_message(span_boldwarning("[victim] falls into the [chasm]!")) //To freak out any bystanders
return fall_into_chasm ? CHASM_DROPPING : CHASM_NOT_DROPPING
return CHASM_DROPPING
#undef CHASM_NOT_DROPPING
#undef CHASM_DROPPING
#undef CHASM_REGISTER_SIGNALS
/datum/component/chasm/proc/drop(atom/movable/dropped_thing)
var/datum/weakref/falling_ref = WEAKREF(dropped_thing)
//Make sure the item is still there after our sleep
if(!dropped_thing || !falling_ref?.resolve())
falling_atoms -= falling_ref
return
falling_atoms[falling_ref] = (falling_atoms[falling_ref] || 0) + 1
var/turf/below_turf = target_turf
var/atom/parent = src.parent
if(falling_atoms[falling_ref] > 1)
return // We're already handling this
if(below_turf)
if(HAS_TRAIT(dropped_thing, TRAIT_CHASM_DESTROYED))
qdel(dropped_thing)
return
// send to the turf below
dropped_thing.visible_message(span_boldwarning("[dropped_thing] falls into [parent]!"), span_userdanger("[fall_message]"))
below_turf.visible_message(span_boldwarning("[dropped_thing] falls from above!"))
dropped_thing.forceMove(below_turf)
if(isliving(dropped_thing))
var/mob/living/fallen = dropped_thing
fallen.Paralyze(100)
fallen.adjustBruteLoss(30)
falling_atoms -= falling_ref
return
// send to oblivion
dropped_thing.visible_message(span_boldwarning("[dropped_thing] falls into [parent]!"), span_userdanger("[oblivion_message]"))
if (isliving(dropped_thing))
var/mob/living/falling_mob = dropped_thing
ADD_TRAIT(falling_mob, TRAIT_NO_TRANSFORM, REF(src))
falling_mob.Paralyze(20 SECONDS)
var/oldtransform = dropped_thing.transform
var/oldcolor = dropped_thing.color
var/oldalpha = dropped_thing.alpha
var/oldoffset = dropped_thing.pixel_y
animate(dropped_thing, transform = matrix() - matrix(), alpha = 0, color = rgb(0, 0, 0), time = 10)
for(var/i in 1 to 5)
//Make sure the item is still there after our sleep
if(!dropped_thing || QDELETED(dropped_thing))
return
dropped_thing.pixel_y--
sleep(0.2 SECONDS)
//Make sure the item is still there after our sleep
if(!dropped_thing || QDELETED(dropped_thing))
return
if(HAS_TRAIT(dropped_thing, TRAIT_CHASM_DESTROYED))
qdel(dropped_thing)
return
if(!storage)
storage = (locate() in parent) || new(parent)
if(storage.contains(dropped_thing))
return
dropped_thing.alpha = oldalpha
dropped_thing.color = oldcolor
dropped_thing.transform = oldtransform
dropped_thing.pixel_y = oldoffset
if(!dropped_thing.forceMove(storage))
parent.visible_message(span_boldwarning("[parent] spits out [dropped_thing]!"))
dropped_thing.throw_at(get_edge_target_turf(parent, pick(GLOB.alldirs)), rand(1, 10), rand(1, 10))
else if(isliving(dropped_thing))
var/mob/living/fallen_mob = dropped_thing
REMOVE_TRAIT(fallen_mob, TRAIT_NO_TRANSFORM, REF(src))
if (fallen_mob.stat != DEAD)
fallen_mob.investigate_log("has died from falling into a chasm.", INVESTIGATE_DEATHS)
if(issilicon(fallen_mob))
//Silicons are held together by hopes and dreams, unfortunately, I'm having a nightmare
var/mob/living/silicon/robot/fallen_borg = fallen_mob
fallen_borg.mmi = null
fallen_mob.death(TRUE)
fallen_mob.apply_damage(300)
falling_atoms -= falling_ref
/**
* Called when something has left the chasm depths storage.
* Arguments
*
* * source - Chasm object holder.
* * gone - Item which has just left the chasm contents.
*/
/datum/component/chasm/proc/left_chasm(atom/source, atom/movable/gone)
SIGNAL_HANDLER
UnregisterSignal(gone, COMSIG_LIVING_REVIVE)
///Global list needed to let fishermen with a rescue hook fish fallen mobs from any place
GLOBAL_LIST_EMPTY(chasm_fallen_mobs)
/**
* An abstract object which is basically just a bag that the chasm puts people inside
*/
/obj/effect/abstract/chasm_storage
name = "chasm depths"
desc = "The bottom of a hole. You shouldn't be able to interact with this."
anchored = TRUE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/obj/effect/abstract/chasm_storage/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_SECLUDED_LOCATION, INNATE_TRAIT)
/obj/effect/abstract/chasm_storage/Entered(atom/movable/arrived)
. = ..()
if(isliving(arrived))
//Mobs that have fallen in reserved area should be deleted to avoid fishing stuff from the deathmatch or VR.
if(is_reserved_level(loc.z) && !istype(get_area(loc), /area/shuttle))
qdel(arrived)
return
RegisterSignal(arrived, COMSIG_LIVING_REVIVE, PROC_REF(on_revive))
LAZYADD(GLOB.chasm_fallen_mobs[get_chasm_category(loc)], arrived)
/obj/effect/abstract/chasm_storage/Exited(atom/movable/gone)
. = ..()
if(isliving(gone))
UnregisterSignal(gone, COMSIG_LIVING_REVIVE)
LAZYREMOVE(GLOB.chasm_fallen_mobs[get_chasm_category(loc)], gone)
/obj/effect/abstract/chasm_storage/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
. = ..()
var/old_cat = get_chasm_category(old_turf)
var/new_cat = get_chasm_category(new_turf)
var/list/mobs = list()
for(var/mob/fallen in src)
mobs += fallen
LAZYREMOVE(GLOB.chasm_fallen_mobs[old_cat], mobs)
LAZYADD(GLOB.chasm_fallen_mobs[new_cat], mobs)
/**
* Returns a key to store, remove and access fallen mobs depending on the z-level.
* This stops rescuing people from places that are waaaaaaaay too far-fetched.
*/
/proc/get_chasm_category(turf/turf)
var/z_level = turf?.z
var/area/area = get_area(turf)
if(istype(area, /area/shuttle)) //shuttle move between z-levels, so they're a special case.
return area
if(is_away_level(z_level))
return ZTRAIT_AWAY
if(is_mining_level(z_level))
return ZTRAIT_MINING
if(is_station_level(z_level))
return ZTRAIT_STATION
if(is_centcom_level(z_level))
return ZTRAIT_CENTCOM
if(is_reserved_level(z_level))
return ZTRAIT_RESERVED
return ZTRAIT_SPACE_RUINS
#define CHASM_TRAIT "chasm trait"
/**
* Called if something comes back to life inside the pit. Expected sources are badmins and changelings.
* Ethereals should take enough damage to be smashed and not revive.
* Arguments
* escapee - Lucky guy who just came back to life at the bottom of a hole.
*/
/obj/effect/abstract/chasm_storage/proc/on_revive(mob/living/escapee)
SIGNAL_HANDLER
var/turf/turf = get_turf(src)
if(turf.GetComponent(/datum/component/chasm))
turf.visible_message(span_boldwarning("After a long climb, [escapee] leaps out of [turf]!"))
else
playsound(turf, 'sound/effects/bang.ogg', 50, TRUE)
turf.visible_message(span_boldwarning("[escapee] busts through [turf], leaping out of the chasm below"))
turf.ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
ADD_TRAIT(escapee, TRAIT_MOVE_FLYING, CHASM_TRAIT) //Otherwise they instantly fall back in
escapee.forceMove(turf)
escapee.throw_at(get_edge_target_turf(turf, pick(GLOB.alldirs)), rand(1, 10), rand(1, 10))
REMOVE_TRAIT(escapee, TRAIT_MOVE_FLYING, CHASM_TRAIT)
escapee.Paralyze(20 SECONDS, TRUE)
UnregisterSignal(escapee, COMSIG_LIVING_REVIVE)
#undef CHASM_TRAIT