-
Notifications
You must be signed in to change notification settings - Fork 67
/
Copy pathfish.dm
519 lines (438 loc) · 18.1 KB
/
fish.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
// Fish path used for autogenerated fish
/obj/item/fish
name = "generic looking aquarium fish"
desc = "very bland"
icon = 'icons/obj/aquarium.dmi'
icon_state = "bugfish"
w_class = WEIGHT_CLASS_TINY
/// Resulting width of aquarium visual icon - default size of "fish_greyscale" state
var/sprite_width = 3
/// Resulting height of aquarium visual icon - default size of "fish_greyscale" state
var/sprite_height = 3
/// Original width of aquarium visual icon - used to calculate scaledown factor
var/source_width = 32
/// Original height of aquarium visual icon - used to calculate scaledown factor
var/source_height = 32
/// If present this icon will be used for in-aquarium visual for the fish instead of icon_state
var/dedicated_in_aquarium_icon_state
/// If present aquarium visual will be this color
var/aquarium_vc_color
/// Required fluid type for this fish to live.
var/required_fluid_type = AQUARIUM_FLUID_FRESHWATER
/// Required minimum temperature for the fish to live.
var/required_temperature_min = MIN_AQUARIUM_TEMP
/// Maximum possible temperature for the fish to live.
var/required_temperature_max = MAX_AQUARIUM_TEMP
/// What type of reagent this fish needs to be fed.
var/food = /datum/reagent/consumable/nutriment
/// How often the fish needs to be fed
var/feeding_frequency = 5 MINUTES
/// Time of last feedeing
var/last_feeding
/// Fish status
var/status = FISH_ALIVE
/// Current fish health. Dies at 0.
var/health = 100
/// Should this fish type show in fish catalog
var/show_in_catalog = TRUE
/// Should this fish spawn in random fish cases
var/available_in_random_cases = TRUE
/// How rare this fish is in the random cases
var/random_case_rarity = FISH_RARITY_BASIC
/// Fish autogenerated from this behaviour will be processable into this
var/fillet_type = /obj/item/food/fishmeat
/// Won't breed more than this amount in single aquarium.
var/stable_population = 1
/// Last time new fish was created
var/last_breeding
/// How long it takes to produce new fish
var/breeding_timeout = 2 MINUTES
var/flopping = FALSE
var/in_stasis = FALSE
/obj/item/fish/Initialize(mapload)
. = ..()
if(fillet_type)
AddElement(/datum/element/processable, TOOL_KNIFE, fillet_type, 1, 5)
AddComponent(/datum/component/aquarium_content, PROC_REF(get_aquarium_animation), list(COMSIG_FISH_STATUS_CHANGED,COMSIG_FISH_STIRRED))
RegisterSignal(src, COMSIG_ATOM_TEMPORARY_ANIMATION_START, PROC_REF(on_temp_animation))
check_environment_after_movement()
if(status != FISH_DEAD)
START_PROCESSING(SSobj, src)
/obj/item/fish/Destroy()
STOP_PROCESSING(SSobj, src)
return ..()
/obj/item/fish/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
check_environment_after_movement()
/obj/item/fish/proc/enter_stasis()
in_stasis = TRUE
// Stop processing until inserted into aquarium again.
stop_flopping()
STOP_PROCESSING(SSobj, src)
/obj/item/fish/proc/exit_stasis()
in_stasis = FALSE
if(status != FISH_DEAD)
START_PROCESSING(SSobj, src)
/obj/item/fish/proc/on_aquarium_insertion(obj/structure/aquarium)
if(isnull(last_feeding)) //Fish start fed.
last_feeding = world.time
RegisterSignal(aquarium, COMSIG_ATOM_EXITED, PROC_REF(aquarium_exited))
RegisterSignal(aquarium, COMSIG_PARENT_ATTACKBY, PROC_REF(attack_reaction))
/obj/item/fish/proc/aquarium_exited(datum/source, atom/movable/gone, direction)
SIGNAL_HANDLER
if(src != gone)
return
UnregisterSignal(source,list(COMSIG_ATOM_EXITED,COMSIG_PARENT_ATTACKBY))
/// Our aquarium is hit with stuff
/obj/item/fish/proc/attack_reaction(datum/source, obj/item/thing, mob/user, params)
SIGNAL_HANDLER
if(is_food(thing))
on_feeding(thing.reagents)
return COMPONENT_NO_AFTERATTACK
else
//stirred effect
SEND_SIGNAL(src, COMSIG_FISH_STIRRED)
/obj/item/fish/proc/is_food(obj/item/thing)
return istype(thing, /obj/item/fish_feed)
/obj/item/fish/proc/on_feeding(datum/reagents/feed_reagents)
if(feed_reagents.has_reagent(food))
last_feeding = world.time
/obj/item/fish/proc/check_environment_after_movement()
if(QDELETED(src)) //we don't care anymore
return
// Apply/remove stasis as needed
if(HAS_TRAIT(loc, TRAIT_FISH_SAFE_STORAGE))
enter_stasis()
else if(in_stasis)
exit_stasis()
// Do additional stuff
var/in_aquarium = istype(loc,/obj/structure/aquarium)
if(in_aquarium)
on_aquarium_insertion(loc)
// Start flopping if outside of fish container
var/should_be_flopping = status == FISH_ALIVE && !HAS_TRAIT(loc,TRAIT_FISH_SAFE_STORAGE) && !in_aquarium
if(should_be_flopping)
start_flopping()
else
stop_flopping()
/obj/item/fish/process(delta_time)
if(in_stasis || status != FISH_ALIVE)
return
process_health(delta_time)
if(ready_to_reproduce())
try_to_reproduce()
/obj/item/fish/proc/set_status(new_status)
switch(new_status)
if(FISH_ALIVE)
status = FISH_ALIVE
health = initial(health) // this is admin option anyway
START_PROCESSING(SSobj, src)
if(FISH_DEAD)
status = FISH_DEAD
STOP_PROCESSING(SSobj, src)
stop_flopping()
var/message = span_notice("\The [name] dies.")
if(istype(loc,/obj/structure/aquarium))
loc.visible_message(message)
else
visible_message(message)
SEND_SIGNAL(src, COMSIG_FISH_STATUS_CHANGED)
/obj/item/fish/proc/get_aquarium_animation()
var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium) || aquarium.fluid_type == AQUARIUM_FLUID_AIR || status == FISH_DEAD)
return AQUARIUM_ANIMATION_FISH_DEAD
else
return AQUARIUM_ANIMATION_FISH_SWIM
/// Checks if our current environment lets us live.
/obj/item/fish/proc/proper_environment()
var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium))
return FALSE
if(required_fluid_type != AQUARIUM_FLUID_ANADROMOUS)
if(aquarium.fluid_type != required_fluid_type)
return FALSE
else
if(aquarium.fluid_type != AQUARIUM_FLUID_SALTWATER && aquarium.fluid_type != AQUARIUM_FLUID_FRESHWATER)
return FALSE
if(aquarium.fluid_temp < required_temperature_min || aquarium.fluid_temp > required_temperature_max)
return FALSE
return TRUE
/obj/item/fish/proc/process_health(delta_time)
var/health_change_per_second = 0
if(!proper_environment())
health_change_per_second -= 3 //Dying here
if(world.time - last_feeding >= feeding_frequency)
health_change_per_second -= 0.5 //Starving
else
health_change_per_second += 0.5 //Slowly healing
adjust_health(health + health_change_per_second * delta_time)
/obj/item/fish/proc/adjust_health(amt)
health = clamp(amt, 0, initial(health))
if(health <= 0)
set_status(FISH_DEAD)
/obj/item/fish/proc/ready_to_reproduce()
var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium))
return FALSE
return aquarium.allow_breeding && health == initial(health) && stable_population > 1 && world.time - last_breeding >= breeding_timeout
//Fish breeding stops if fish count exceeds this.
#define AQUARIUM_MAX_BREEDING_POPULATION 20
/obj/item/fish/proc/try_to_reproduce()
var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium))
return
if(length(aquarium.tracked_fish) >= AQUARIUM_MAX_BREEDING_POPULATION) //so aquariums full of fish don't need to do these expensive checks
return
var/list/other_fish_of_same_type = list()
for(var/obj/item/fish/fish_in_aquarium in aquarium)
if(fish_in_aquarium == src || fish_in_aquarium.type != type)
continue
other_fish_of_same_type += fish_in_aquarium
if(length(other_fish_of_same_type) >= stable_population)
return
var/obj/item/fish/second_fish
for(var/obj/item/fish/other_fish in other_fish_of_same_type)
if(other_fish.ready_to_reproduce())
second_fish = other_fish
break
if(second_fish)
new type(loc) //could use child_type var
last_breeding = world.time
second_fish.last_breeding = world.time
#undef AQUARIUM_MAX_BREEDING_POPULATION
#define PAUSE_BETWEEN_PHASES 15
#define PAUSE_BETWEEN_FLOPS 2
#define FLOP_COUNT 2
#define FLOP_DEGREE 20
#define FLOP_SINGLE_MOVE_TIME 1.5
#define JUMP_X_DISTANCE 5
#define JUMP_Y_DISTANCE 6
/// This animation should be applied to actual parent atom instead of vc_object.
/proc/flop_animation(atom/movable/animation_target)
var/pause_between = PAUSE_BETWEEN_PHASES + rand(1, 5) //randomized a bit so fish are not in sync
animate(animation_target, time = pause_between, loop = -1)
//move nose down and up
for(var/_ in 1 to FLOP_COUNT)
var/matrix/up_matrix = matrix()
up_matrix.Turn(FLOP_DEGREE)
var/matrix/down_matrix = matrix()
down_matrix.Turn(-FLOP_DEGREE)
animate(transform = down_matrix, time = FLOP_SINGLE_MOVE_TIME, loop = -1)
animate(transform = up_matrix, time = FLOP_SINGLE_MOVE_TIME, loop = -1)
animate(transform = matrix(), time = FLOP_SINGLE_MOVE_TIME, loop = -1, easing = BOUNCE_EASING | EASE_IN)
animate(time = PAUSE_BETWEEN_FLOPS, loop = -1)
//bounce up and down
animate(time = pause_between, loop = -1, flags = ANIMATION_PARALLEL)
var/jumping_right = FALSE
var/up_time = 3 * FLOP_SINGLE_MOVE_TIME / 2
for(var/_ in 1 to FLOP_COUNT)
jumping_right = !jumping_right
var/x_step = jumping_right ? JUMP_X_DISTANCE/2 : -JUMP_X_DISTANCE/2
animate(time = up_time, pixel_y = JUMP_Y_DISTANCE , pixel_x=x_step, loop = -1, flags= ANIMATION_RELATIVE, easing = BOUNCE_EASING | EASE_IN)
animate(time = up_time, pixel_y = -JUMP_Y_DISTANCE, pixel_x=x_step, loop = -1, flags= ANIMATION_RELATIVE, easing = BOUNCE_EASING | EASE_OUT)
animate(time = PAUSE_BETWEEN_FLOPS, loop = -1)
#undef PAUSE_BETWEEN_PHASES
#undef PAUSE_BETWEEN_FLOPS
#undef FLOP_COUNT
#undef FLOP_DEGREE
#undef FLOP_SINGLE_MOVE_TIME
#undef JUMP_X_DISTANCE
#undef JUMP_Y_DISTANCE
/// Starts flopping animation
/obj/item/fish/proc/start_flopping()
if(!flopping) //Requires update_transform/animate_wrappers to be less restrictive.
flopping = TRUE
flop_animation(src)
/// Stops flopping animation
/obj/item/fish/proc/stop_flopping()
if(flopping)
flopping = FALSE
animate(src, transform = matrix()) //stop animation
/// Refreshes flopping animation after temporary animation finishes
/obj/item/fish/proc/on_temp_animation(datum/source, animation_duration)
if(animation_duration > 0)
addtimer(CALLBACK(src, PROC_REF(refresh_flopping)), animation_duration)
/obj/item/fish/proc/refresh_flopping()
if(flopping)
flop_animation(src)
/// Returns random fish, using random_case_rarity probabilities.
/proc/random_fish_type(case_fish_only=TRUE, required_fluid)
var/static/probability_table
var/argkey = "fish_[required_fluid]_[case_fish_only]" //If this expands more extract bespoke element arg generation to some common helper.
if(!probability_table || !probability_table[argkey])
if(!probability_table)
probability_table = list()
var/chance_table = list()
for(var/_fish_type in subtypesof(/obj/item/fish))
var/obj/item/fish/fish = _fish_type
if(required_fluid && initial(fish.required_fluid_type) != required_fluid)
continue
if(initial(fish.available_in_random_cases) || !case_fish_only)
chance_table[fish] = initial(fish.random_case_rarity)
probability_table[argkey] = chance_table
return pick_weight(probability_table[argkey])
// Freshwater fish
/obj/item/fish/goldfish
name = "goldfish"
desc = "Despite common belief, goldfish do not have three-second memories. They can actually remember things that happened up to three months ago."
icon_state = "goldfish"
sprite_width = 8
sprite_height = 8
stable_population = 3
/obj/item/fish/angelfish
name = "angelfish"
desc = "Young Angelfish often live in groups, while adults prefer solitary life. They become territorial and aggressive toward other fish when they reach adulthood."
icon_state = "angelfish"
dedicated_in_aquarium_icon_state = "bigfish"
sprite_height = 7
source_height = 7
stable_population = 3
/obj/item/fish/guppy
name = "guppy"
desc = "Guppy is also known as rainbow fish because of the brightly colored body and fins."
icon_state = "guppy"
dedicated_in_aquarium_icon_state = "fish_greyscale"
aquarium_vc_color = "#91AE64"
sprite_width = 8
sprite_height = 5
stable_population = 6
/obj/item/fish/plasmatetra
name = "plasma tetra"
desc = "Due to their small size, tetras are prey to many predators in their watery world, including eels, crustaceans, and invertebrates."
icon_state = "plastetra"
dedicated_in_aquarium_icon_state = "fish_greyscale"
aquarium_vc_color = "#D30EB0"
stable_population = 3
/obj/item/fish/catfish
name = "cory catfish"
desc = "A catfish has about 100,000 taste buds, and their bodies are covered with them to help detect chemicals present in the water and also to respond to touch."
icon_state = "catfish"
dedicated_in_aquarium_icon_state = "fish_greyscale"
aquarium_vc_color = "#907420"
stable_population = 3
// Saltwater fish below
/obj/item/fish/clownfish
name = "clownfish"
desc = "Clownfish catch prey by swimming onto the reef, attracting larger fish, and luring them back to the anemone. The anemone will sting and eat the larger fish, leaving the remains for the clownfish."
icon_state = "clownfish"
dedicated_in_aquarium_icon_state = "clownfish_small"
required_fluid_type = AQUARIUM_FLUID_SALTWATER
sprite_width = 8
sprite_height = 5
stable_population = 4
/obj/item/fish/cardinal
name = "cardinalfish"
desc = "Cardinalfish are often found near sea urchins, where the fish hide when threatened."
icon_state = "cardinalfish"
dedicated_in_aquarium_icon_state = "fish_greyscale"
required_fluid_type = AQUARIUM_FLUID_SALTWATER
stable_population = 4
/obj/item/fish/greenchromis
name = "green chromis"
desc = "The Chromis can vary in color from blue to green depending on the lighting and distance from the lights."
icon_state = "greenchromis"
dedicated_in_aquarium_icon_state = "fish_greyscale"
aquarium_vc_color = "#00ff00"
required_fluid_type = AQUARIUM_FLUID_SALTWATER
stable_population = 5
/obj/item/fish/firefish
name = "firefish goby"
desc = "To communicate in the wild, the firefish uses its dorsal fin to alert others of potential danger."
icon_state = "firefish"
sprite_width = 6
sprite_height = 5
required_fluid_type = AQUARIUM_FLUID_SALTWATER
stable_population = 3
/obj/item/fish/pufferfish
name = "pufferfish"
desc = "One Pufferfish contains enough toxins in its liver to kill 30 people."
icon_state = "pufferfish"
required_fluid_type = AQUARIUM_FLUID_SALTWATER
sprite_width = 8
sprite_height = 8
stable_population = 3
/obj/item/fish/lanternfish
name = "lanternfish"
desc = "Typically found in areas below 6600 feet below the surface of the ocean, they live in complete darkness."
icon_state = "lanternfish"
required_fluid_type = AQUARIUM_FLUID_SALTWATER
random_case_rarity = FISH_RARITY_VERY_RARE
source_width = 28
source_height = 21
sprite_width = 8
sprite_height = 8
stable_population = 3
//Jitarai Fish
/obj/item/fish/dwarf_moonfish
name = "dwarf moonfish"
desc = "Ordinarily in the wild, the Zagoskian moonfish is around the size of a tuna, however through selective breeding a smaller breed suitable for being kept as an aquarium pet has been created."
icon_state = "dwarf_moonfish"
required_fluid_type = AQUARIUM_FLUID_SALTWATER
stable_population = 2
fillet_type = /obj/item/food/fishmeat/moonfish
/obj/item/fish/gunner_jellyfish
name = "gunner jellyfish"
desc = "So called due to their resemblance to an artillery shell, the gunner jellyfish is native to Jitarai, where it is enjoyed as a delicacy. Produces a mild hallucinogen that is destroyed by cooking."
icon_state = "gunner_jellyfish"
required_fluid_type = AQUARIUM_FLUID_SALTWATER
stable_population = 4
fillet_type = /obj/item/food/fishmeat/gunner_jellyfish
/obj/item/fish/needlefish
name = "needlefish"
desc = "A tiny, transparent fish which resides in large schools in the oceans of Jitarai. A common food for other, larger fish."
icon_state = "needlefish"
required_fluid_type = AQUARIUM_FLUID_SALTWATER
stable_population = 12
fillet_type = null
/obj/item/fish/armorfish
name = "armorfish"
desc = "A small shellfish native to Jitarai's oceans, known for its exceptionally hard shell. Consumed similarly to prawns."
icon_state = "armorfish"
required_fluid_type = AQUARIUM_FLUID_SALTWATER
stable_population = 10
fillet_type = /obj/item/food/fishmeat/armorfish
/obj/item/storage/box/fish_debug
name = "box full of fish"
/obj/item/storage/box/fish_debug/PopulateContents()
for(var/fish_type in subtypesof(/obj/item/fish))
new fish_type(src)
/obj/item/fish/donkfish
name = "donk co. company patent donkfish"
desc = "A lab-grown donkfish. Its invention was an accident for the most part, as it was intended to be consumed in donk pockets. Unfortunately, it tastes horrible, so it has now become a pseudo-mascot."
icon_state = "donkfish"
random_case_rarity = FISH_RARITY_VERY_RARE
required_fluid_type = AQUARIUM_FLUID_FRESHWATER
stable_population = 4
fillet_type = /obj/item/food/fishmeat/donkfish
/obj/item/fish/emulsijack
name = "toxic emulsijack"
desc = "Ah, the terrifying emulsijack. Created in a laboratory, this slimey, scaleless fish emits an invisible toxin that emulsifies other fish for it to feed on. Its only real use is for completely ruining a tank."
icon_state = "emulsijack"
random_case_rarity = FISH_RARITY_GOOD_LUCK_FINDING_THIS
required_fluid_type = AQUARIUM_FLUID_ANADROMOUS
stable_population = 3
/obj/item/fish/emulsijack/process(delta_time = SSOBJ_DT)
var/emulsified = FALSE
var/obj/structure/aquarium/aquarium = loc
if(istype(aquarium))
for(var/obj/item/fish/victim in aquarium)
if(istype(victim, /obj/item/fish/emulsijack))
continue //no team killing
victim.adjust_health((victim.health - 3) * delta_time) //the victim may heal a bit but this will quickly kill
emulsified = TRUE
if(emulsified)
adjust_health((health + 3) * delta_time)
last_feeding = world.time //emulsijack feeds on the emulsion!
..()
/obj/item/fish/ratfish
name = "ratfish"
desc = "A rat exposed to the murky waters of maintenance too long. Any higher power, if it revealed itself, would state that the ratfish's continued existence is extremely unwelcome."
icon_state = "ratfish"
random_case_rarity = FISH_RARITY_RARE
required_fluid_type = AQUARIUM_FLUID_FRESHWATER
stable_population = 10 //set by New, but this is the default config value
fillet_type = /obj/item/food/meat/slab/human/mutant/zombie //eww...
/obj/item/fish/ratfish/Initialize(mapload)
. = ..()
//stable pop reflects the config for how many mice migrate. powerful...
stable_population = CONFIG_GET(number/mice_roundstart)