mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-21 13:46:55 +01:00
- all cells in the game are now small, medium, large - super, hyper capacity cells, device, and weapon cells are all removed - cells now have standardized variants, some of which are printable by R&D - energy weapons rebalanced - **advanced energy guns and stun revolvers removed** - use the new modular weapon system and microfission cells. --------- Co-authored-by: silicons <silicons@silicons.dev>
548 lines
18 KiB
Plaintext
548 lines
18 KiB
Plaintext
//* This file is explicitly licensed under the MIT license. *//
|
|
//* Copyright (c) 2024 Citadel Station Developers *//
|
|
|
|
/**
|
|
* creates a segmenting beam
|
|
*
|
|
* you must delete this beam yourself, unlike legacy beams
|
|
*
|
|
* @params
|
|
* * source - where to draw from
|
|
* * target - where to draw to
|
|
* * collider_type - set to a type to create colliders
|
|
* * icon - beam icon
|
|
* * icon_state - beam icon state
|
|
* * emissive_state - emissive overlay to use
|
|
*/
|
|
/proc/create_segmented_beam(atom/source, atom/target, collider_type, icon, icon_state, emissive_state)
|
|
var/datum/beam/created = new(source, target)
|
|
if(!isnull(collider_type))
|
|
created.collider_type = collider_type
|
|
created.icon = icon
|
|
created.icon_state = icon_state
|
|
created.emissive_state = emissive_state
|
|
created.beam_visual_mode = BEAM_VISUAL_SEGMENTS
|
|
created.initialize()
|
|
return created
|
|
|
|
/**
|
|
* creates a stretching beam
|
|
*
|
|
* you must delete this beam yourself, unlike legacy beams
|
|
*
|
|
* @params
|
|
* * source - where to draw from
|
|
* * target - where to draw to
|
|
* * collider_type - set to a type to create colliders
|
|
* * icon - beam icon
|
|
* * icon_state - beam icon state
|
|
* * emissive_state - emissive overlay to use
|
|
*/
|
|
/proc/create_stretched_beam(atom/source, atom/target, collider_type, icon, icon_state, emissive_state)
|
|
var/datum/beam/created = new(source, target)
|
|
if(!isnull(collider_type))
|
|
created.collider_type = collider_type
|
|
created.icon = icon
|
|
created.icon_state = icon_state
|
|
created.emissive_state = emissive_state
|
|
created.beam_visual_mode = BEAM_VISUAL_STRETCH
|
|
created.initialize()
|
|
return created
|
|
|
|
/**
|
|
* creates a beam that... doesn't render (lmao)
|
|
*
|
|
* you must delete this beam yourself, unlike legacy beams
|
|
*
|
|
* @params
|
|
* * source - where to draw from
|
|
* * target - where to draw to
|
|
* * collider_type - set to a type to create colliders
|
|
*/
|
|
/proc/create_particle_beam(atom/source, atom/target, collider_type)
|
|
var/datum/beam/created = new(source, target)
|
|
if(!isnull(collider_type))
|
|
created.collider_type = collider_type
|
|
created.initialize()
|
|
return created
|
|
|
|
/**
|
|
* linear on-map line drawing datums
|
|
*
|
|
* can optionally handle colliders.
|
|
*
|
|
* * beam_source, beam_target are both get_turf()'d. it is *your* job to handle whether or not this is valid
|
|
* * beam drawing is event-driven. it doesn't redraw unless necessary.
|
|
*/
|
|
/datum/beam
|
|
//* basics *//
|
|
/// source
|
|
var/atom/beam_source
|
|
/// target
|
|
var/atom/beam_target
|
|
/// listener for source
|
|
var/datum/beam_listener/listener_source
|
|
/// listener for target
|
|
var/datum/beam_listener/listener_target
|
|
/// distance to bias towards target from source
|
|
var/shift_start_towards_target = 0
|
|
/// distance to bias towards source from target
|
|
var/shift_end_towards_source = 0
|
|
/// do not automatically redraw on things moving
|
|
var/no_automatic_redraw = FALSE
|
|
|
|
//* collision *//
|
|
/// collider type to use
|
|
var/collider_type
|
|
/// registered colliding entities, turf or movable; only made as a list if collision or segment mode is enabled
|
|
var/list/atom/colliding
|
|
/// colliders
|
|
var/list/atom/movable/beam_collider/colliders
|
|
|
|
//* segmentation *//
|
|
/// beam segment holder
|
|
var/atom/movable/render/beam_segments/segmentation
|
|
/// beam segment holder
|
|
var/atom/movable/render/beam_segments/emissive/emissive_segmentation
|
|
|
|
//* graphics *//
|
|
/// graphics mode
|
|
var/beam_visual_mode
|
|
/// icon to use in non-particle mode
|
|
var/icon
|
|
/// state to use in non-particle mode
|
|
var/icon_state
|
|
/// emissive state to use in non-particle mode
|
|
var/emissive_state
|
|
/// if we are in stretch mode, this is our rendering line
|
|
var/atom/movable/render/beam_line/line_renderer
|
|
/// if we are in stretch mode, this is our emissive rendering line
|
|
var/atom/movable/render/beam_line/emissive/emissive_line_renderer
|
|
/// if we are in particle mode, this is our renderer
|
|
var/atom/movable/render/particle/beam_line/particle_renderer
|
|
/// if we are in particle mode, this is our emissive renderer
|
|
var/atom/movable/render/particle/beam_line/emissive/emissive_particle_renderer
|
|
/// if we are in particle mode, this is the particle instance we use.
|
|
var/particles/particle_reference
|
|
/// if we are in particle mode, tihs is the emissive particle instance we use.
|
|
var/particles/emissive_particle_reference
|
|
|
|
/datum/beam/New(atom/source, atom/target)
|
|
src.beam_source = source
|
|
src.beam_target = target
|
|
register()
|
|
|
|
/datum/beam/Destroy()
|
|
unregister()
|
|
beam_source = null
|
|
beam_target = null
|
|
// get rid of emissives first
|
|
QDEL_NULL(emissive_line_renderer)
|
|
QDEL_NULL(line_renderer)
|
|
QDEL_NULL(emissive_particle_renderer)
|
|
QDEL_NULL(particle_renderer)
|
|
QDEL_NULL(emissive_segmentation)
|
|
QDEL_NULL(segmentation)
|
|
// get rid of colliders
|
|
QDEL_LIST_NULL(colliders)
|
|
return ..()
|
|
|
|
/datum/beam/proc/register()
|
|
listener_source = new(src, beam_source)
|
|
listener_target = new(src, beam_target)
|
|
|
|
/datum/beam/proc/unregister()
|
|
QDEL_NULL(listener_source)
|
|
QDEL_NULL(listener_target)
|
|
|
|
/datum/beam/proc/initialize()
|
|
// for any modes with emissives, the emissive is shoved into the main renderer
|
|
// and the entire thing is KT-group'd together.
|
|
switch(beam_visual_mode)
|
|
if(BEAM_VISUAL_SEGMENTS)
|
|
segmentation = new
|
|
segmentation.icon = icon
|
|
if(!isnull(emissive_state))
|
|
emissive_segmentation = new(segmentation)
|
|
emissive_segmentation.icon = icon
|
|
if(BEAM_VISUAL_STRETCH)
|
|
line_renderer = new
|
|
line_renderer.icon = icon
|
|
line_renderer.icon_state = icon_state
|
|
if(!isnull(emissive_state))
|
|
emissive_line_renderer = new(line_renderer)
|
|
emissive_line_renderer.icon = icon
|
|
emissive_line_renderer.icon_state = emissive_state
|
|
|
|
if(collider_type)
|
|
LAZYINITLIST(colliders)
|
|
LAZYINITLIST(colliding)
|
|
|
|
force_redraw()
|
|
|
|
/datum/beam/proc/force_redraw()
|
|
var/turf/start = get_turf(beam_source)
|
|
var/turf/end = get_turf(beam_target)
|
|
var/spx = 0
|
|
var/spy = 0
|
|
var/epx = 0
|
|
var/epy = 0
|
|
if(isturf(beam_source.loc))
|
|
var/atom/movable/movable_source = beam_source
|
|
spx = movable_source.pixel_x
|
|
spy = movable_source.pixel_y
|
|
if(isturf(beam_target.loc))
|
|
var/atom/movable/movable_target = beam_target
|
|
epx = movable_target.pixel_x
|
|
epy = movable_target.pixel_y
|
|
render(start, spx, spy, end, epx, epy)
|
|
SEND_SIGNAL(src, COMSIG_BEAM_REDRAW)
|
|
|
|
/datum/beam/proc/signal_redraw(atom/movable/source, atom/old_loc, dir, forced, list/old_locs, momentum_change)
|
|
if(ismovable(old_loc))
|
|
// get old turf
|
|
var/turf/was_at = get_turf(old_loc)
|
|
// signal was emitted on an intermediate object instead of top level; drop signals there
|
|
var/atom/iterating
|
|
iterating = old_loc
|
|
do
|
|
UnregisterSignal(iterating, COMSIG_MOVABLE_MOVED)
|
|
iterating = iterating.loc
|
|
while(ismovable(iterating))
|
|
// and register to the new intermediate object, if any
|
|
if(ismovable(source.loc))
|
|
iterating = source.loc
|
|
do
|
|
RegisterSignal(iterating, COMSIG_MOVABLE_MOVED, PROC_REF(signal_redraw))
|
|
iterating = iterating.loc
|
|
while(ismovable(iterating))
|
|
// get new turf
|
|
var/turf/is_at = get_turf(source.loc)
|
|
// if both turfs are the same, aka we were shifting internally, don't bother redrawing
|
|
if(was_at == is_at)
|
|
return
|
|
// todo: optimize
|
|
// for now i'm lazy so just do a hard draw instead of a cached draw of some kind
|
|
force_redraw()
|
|
|
|
/datum/beam/proc/render(turf/start, start_px, start_py, turf/end, end_px, end_py)
|
|
// nah.
|
|
if(start.z != end.z || start == end)
|
|
switch(beam_visual_mode)
|
|
if(BEAM_VISUAL_SEGMENTS)
|
|
segmentation?.loc = emissive_segmentation?.loc = null
|
|
if(BEAM_VISUAL_STRETCH)
|
|
line_renderer?.loc = emissive_line_renderer?.loc = null
|
|
particle_renderer?.loc = null
|
|
for(var/atom/movable/beam_collider/collider as anything in colliders)
|
|
collider.move_onto(null)
|
|
return FALSE
|
|
// calculate parameters
|
|
var/dx = ((WORLD_ICON_SIZE * end.x + end_px) - (WORLD_ICON_SIZE * start.x + start_px))
|
|
var/dy = ((WORLD_ICON_SIZE * end.y + end_py) - (WORLD_ICON_SIZE * start.y + start_py))
|
|
// cw from north
|
|
var/angle = arctan(dy, dx)
|
|
// do biases
|
|
// todo: is there is a better way to do this
|
|
if(shift_start_towards_target)
|
|
var/x_amt = shift_start_towards_target * sin(angle)
|
|
var/y_amt = shift_start_towards_target * cos(angle)
|
|
start_px += x_amt
|
|
start_py += y_amt
|
|
dx -= x_amt
|
|
dy -= y_amt
|
|
if(shift_end_towards_source)
|
|
var/x_amt = shift_end_towards_source * sin(angle)
|
|
var/y_amt = shift_end_towards_source * cos(angle)
|
|
end_px -= x_amt
|
|
end_py -= y_amt
|
|
dx -= x_amt
|
|
dy -= y_amt
|
|
// dist in pixels
|
|
var/distance = sqrt(dx ** 2 + dy ** 2)
|
|
// draw
|
|
switch(beam_visual_mode)
|
|
if(BEAM_VISUAL_SEGMENTS)
|
|
// don't stretch past distance
|
|
var/steps_required = CEILING(distance / WORLD_ICON_SIZE, 1)
|
|
// we assume both segment renderers are lockstepped
|
|
var/requires_update = FALSE
|
|
if(steps_required > length(segmentation.segment_appearances))
|
|
requires_update = TRUE
|
|
var/start_from_pixel = length(segmentation.segment_appearances) * WORLD_ICON_SIZE
|
|
for(var/i in 1 to steps_required - length(segmentation.segment_appearances))
|
|
var/image/injecting = image(icon_state = icon_state)
|
|
injecting.pixel_y = i * WORLD_ICON_SIZE - (WORLD_ICON_SIZE * 0.5) + start_from_pixel
|
|
segmentation.segment_appearances += injecting
|
|
if(!isnull(emissive_segmentation))
|
|
for(var/i in 1 to steps_required - length(emissive_segmentation.segment_appearances))
|
|
var/image/emissive_injecting = image(icon_state = emissive_state)
|
|
emissive_injecting.pixel_y = i * WORLD_ICON_SIZE - (WORLD_ICON_SIZE * 0.5) + start_from_pixel
|
|
emissive_segmentation.segment_appearances += emissive_injecting
|
|
else if(steps_required < length(segmentation.segment_appearances))
|
|
requires_update = TRUE
|
|
segmentation.segment_appearances.len = steps_required
|
|
emissive_segmentation?.segment_appearances.len = steps_required
|
|
if(requires_update)
|
|
segmentation.overlays = segmentation.segment_appearances
|
|
emissive_segmentation?.overlays = emissive_segmentation?.segment_appearances
|
|
// calculate matrix
|
|
var/matrix/transform_to_apply = matrix()
|
|
// transform as necessary to shrink just enough to cut off extra pixels
|
|
// todo: please use an alphamask filter on last element maybe, this looks like shit
|
|
transform_to_apply.Scale(1, distance / (steps_required * WORLD_ICON_SIZE))
|
|
// rotate
|
|
transform_to_apply.Turn(angle)
|
|
// move renders to location
|
|
segmentation.forceMove(start)
|
|
segmentation.pixel_x = start_px
|
|
segmentation.pixel_y = start_py
|
|
segmentation.transform = transform_to_apply
|
|
if(BEAM_VISUAL_STRETCH)
|
|
// calculate matrix
|
|
var/matrix/transform_to_apply = matrix()
|
|
// shift up so it's not in the way
|
|
transform_to_apply.Translate(0, 16)
|
|
// scale up the now shifted one so we don't have to translate post-scale
|
|
transform_to_apply.Scale(1, distance / WORLD_ICON_SIZE)
|
|
// rotate
|
|
transform_to_apply.Turn(angle)
|
|
// handle normal render
|
|
line_renderer.forceMove(start)
|
|
line_renderer.pixel_x = start_px
|
|
line_renderer.pixel_y = start_py
|
|
line_renderer.transform = transform_to_apply
|
|
// deal with colliders
|
|
if(!isnull(collider_type))
|
|
var/list/turf/colliding_turfs = getline(start, end)
|
|
if(length(colliding_turfs) < colliders)
|
|
// if more than needed, nullspace the rest but keep them on hand
|
|
for(var/i in length(colliding_turfs) + 1 to length(colliders))
|
|
var/atom/movable/beam_collider/collider = colliders[i]
|
|
collider.move_onto(null)
|
|
else if(length(colliding_turfs) > colliders)
|
|
// if less than needed, make new ones but don't collide yet
|
|
for(var/i in 1 to length(colliding_turfs) - length(colliders))
|
|
colliders += new /atom/movable/beam_collider(src)
|
|
// we should be greater-or-equal colliders than turfs now
|
|
// now-overallocated colliders are ignored and cached in nullspace
|
|
// this system also enforces deterministic source-to-target propagation as opposed
|
|
// to non-deterministic orderings.
|
|
for(var/i in 1 to min(length(colliders), length(colliding_turfs)))
|
|
var/atom/movable/beam_collider/collider = colliders[i]
|
|
collider.move_onto(colliding_turfs[i])
|
|
// deal with particles
|
|
if(!isnull(particle_renderer))
|
|
// calculate matrix
|
|
var/matrix/transform_to_apply = matrix()
|
|
// rotate
|
|
transform_to_apply.Turn(angle)
|
|
// move renders to location
|
|
particle_renderer.forceMove(start)
|
|
particle_renderer.pixel_x = start_px
|
|
particle_renderer.pixel_y = start_py
|
|
particle_renderer.transform = transform_to_apply
|
|
|
|
/datum/beam/proc/uncrossed(atom/movable/entity)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
colliding -= entity
|
|
SEND_SIGNAL(src, COMSIG_BEAM_UNCROSSED, entity)
|
|
|
|
/datum/beam/proc/crossed(atom/movable/entity)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
colliding += entity
|
|
SEND_SIGNAL(src, COMSIG_BEAM_CROSSED, entity)
|
|
|
|
/**
|
|
* adds a set of particles to this beam
|
|
*
|
|
* you have to control the particles yourself
|
|
*
|
|
* * the beam assumes that the particles will project in the **north** direction.
|
|
* * this just makes the beam automatically rotate the particles as necessary.
|
|
* * if you pass in 'TRUE' as emissive_particle_renderer, emissives will render_source from the main particle system.
|
|
*/
|
|
/datum/beam/proc/set_particles(particles/particle_reference, particles/emissive_particle_reference)
|
|
if(isnull(particle_renderer))
|
|
particle_renderer = new
|
|
particle_renderer.particles = particle_reference
|
|
if(!isnull(emissive_particle_reference))
|
|
if(isnull(emissive_particle_renderer))
|
|
emissive_particle_renderer = new
|
|
if(emissive_particle_reference == TRUE)
|
|
particle_renderer.ensure_render_target()
|
|
emissive_particle_renderer.render_source = particle_renderer.render_target
|
|
else
|
|
emissive_particle_renderer.particles = emissive_particle_reference
|
|
else if(!isnull(emissive_particle_renderer))
|
|
QDEL_NULL(emissive_particle_renderer)
|
|
force_redraw()
|
|
|
|
/**
|
|
* tl;dr
|
|
*
|
|
* we don't track the 'real' top level atom in beam
|
|
* so this causes problems since component signal registrations are idempotent for a given (target, listening, signal) tuple
|
|
* we don't want that but that's what makes comsigs efficient
|
|
*
|
|
* so this is here because this means /datum/beam isn't the only datum when source / target are part of the same
|
|
* nested contents tree, avoiding collisions from it
|
|
*/
|
|
/datum/beam_listener
|
|
/// our beam
|
|
var/datum/beam/beam
|
|
/// our target
|
|
var/atom/target
|
|
|
|
/datum/beam_listener/New(datum/beam/beam, atom/target)
|
|
src.beam = beam
|
|
src.target = target
|
|
register()
|
|
|
|
/datum/beam_listener/Destroy()
|
|
unregister()
|
|
return ..()
|
|
|
|
/datum/beam_listener/proc/register()
|
|
if(ismovable(target))
|
|
var/atom/iterating = target
|
|
do
|
|
RegisterSignal(iterating, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
|
|
iterating = iterating.loc
|
|
while(ismovable(iterating))
|
|
|
|
/datum/beam_listener/proc/unregister()
|
|
if(ismovable(target))
|
|
var/atom/iterating = target
|
|
do
|
|
UnregisterSignal(iterating, COMSIG_MOVABLE_MOVED)
|
|
iterating = iterating.loc
|
|
while(ismovable(iterating))
|
|
|
|
/datum/beam_listener/proc/on_move(atom/movable/source, atom/old_loc, dir, forced, list/old_locs, momentum_change)
|
|
beam.signal_redraw(arglist(args))
|
|
|
|
//* Segmented Renderers *//
|
|
|
|
/atom/movable/render/beam_segments
|
|
SET_APPEARANCE_FLAGS(PIXEL_SCALE | KEEP_TOGETHER)
|
|
animate_movement = NONE
|
|
|
|
/// segments in us
|
|
var/list/image/segment_appearances = list()
|
|
|
|
/atom/movable/render/beam_segments/emissive
|
|
plane = EMISSIVE_PLANE
|
|
|
|
/atom/movable/render/beam_segments/emissive/Initialize(mapload)
|
|
ASSERT(istype(loc, /atom/movable/render/beam_segments))
|
|
loc:vis_contents += src
|
|
return ..()
|
|
|
|
/atom/movable/render/beam_segments/emissive/Destroy()
|
|
ASSERT(istype(loc, /atom/movable/render/beam_segments))
|
|
loc:vis_contents -= src
|
|
return ..()
|
|
|
|
//* Colliders *//
|
|
|
|
INITIALIZE_IMMEDIATE(/atom/movable/beam_collider)
|
|
/atom/movable/beam_collider
|
|
atom_flags = ATOM_ABSTRACT
|
|
animate_movement = NONE
|
|
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
|
invisibility = INVISIBILITY_ABSTRACT
|
|
|
|
/// parent beam
|
|
var/datum/beam/parent
|
|
|
|
/atom/movable/beam_collider/Initialize(mapload, datum/beam/parent)
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
src.parent = parent
|
|
atom_flags |= ATOM_INITIALIZED
|
|
return INITIALIZE_HINT_NORMAL
|
|
|
|
/atom/movable/beam_collider/Destroy()
|
|
if(!isnull(parent))
|
|
// make sure we uncross everything!
|
|
move_onto(null)
|
|
parent = null
|
|
return ..()
|
|
|
|
/atom/movable/beam_collider/proc/move_onto(turf/where)
|
|
if(where == loc)
|
|
return
|
|
for(var/atom/movable/other as anything in loc)
|
|
if(other == src)
|
|
continue
|
|
if(other.atom_flags & ATOM_ABSTRACT)
|
|
continue
|
|
parent.uncrossed(other)
|
|
parent.uncrossed(loc)
|
|
abstract_move(where)
|
|
parent.crossed(loc)
|
|
for(var/atom/movable/other as anything in loc)
|
|
if(other == src)
|
|
continue
|
|
if(other.atom_flags & ATOM_ABSTRACT)
|
|
continue
|
|
parent.crossed(other)
|
|
|
|
/atom/movable/beam_collider/Crossed(atom/movable/AM)
|
|
. = ..()
|
|
if(AM.atom_flags & (ATOM_ABSTRACT))
|
|
return
|
|
parent.crossed(AM)
|
|
|
|
/atom/movable/beam_collider/Uncrossed(atom/movable/AM)
|
|
. = ..()
|
|
if(AM.atom_flags & (ATOM_ABSTRACT))
|
|
return
|
|
parent.uncrossed(AM)
|
|
|
|
/atom/movable/beam_collider/Move()
|
|
return FALSE
|
|
|
|
/atom/movable/beam_collider/doMove(atom/destination)
|
|
if(destination == null)
|
|
return ..()
|
|
return FALSE
|
|
|
|
//* Stretched Renderers *//
|
|
|
|
/atom/movable/render/beam_line
|
|
appearance_flags = KEEP_TOGETHER
|
|
animate_movement = NONE
|
|
|
|
/atom/movable/render/beam_line/emissive
|
|
plane = EMISSIVE_PLANE
|
|
|
|
/atom/movable/render/beam_line/emissive/Initialize(mapload)
|
|
ASSERT(istype(loc, /atom/movable/render/beam_line))
|
|
loc:vis_contents += src
|
|
return ..()
|
|
|
|
/atom/movable/render/beam_line/emissive/Destroy()
|
|
ASSERT(istype(loc, /atom/movable/render/beam_line))
|
|
loc:vis_contents -= src
|
|
return ..()
|
|
|
|
//* Particle Renderers *//
|
|
|
|
/atom/movable/render/particle/beam_line
|
|
appearance_flags = KEEP_TOGETHER
|
|
animate_movement = NONE
|
|
|
|
/atom/movable/render/particle/beam_line/emissive
|
|
plane = EMISSIVE_PLANE
|
|
|
|
/atom/movable/render/particle/beam_line/emissive/Initialize(mapload)
|
|
ASSERT(istype(loc, /atom/movable/render/particle/beam_line))
|
|
loc:vis_contents += src
|
|
return ..()
|
|
|
|
/atom/movable/render/particle/beam_line/emissive/Destroy()
|
|
ASSERT(istype(loc, /atom/movable/render/particle/beam_line))
|
|
loc:vis_contents -= src
|
|
return ..()
|