Compare commits

..

7 Commits

Author SHA1 Message Date
9cea72bc2d Documentation fixups. 2023-09-03 16:33:10 -07:00
f3c66cb240 Cacodemon visuals. 2023-09-03 16:20:24 -07:00
960895e24c Added caco plush to menu and made it use spawnflags. 2023-09-03 15:17:19 -07:00
d69894f406 Stream work. 2023-09-03 15:04:00 -07:00
7e081bf91f More caco plushie work. 2023-09-03 10:03:56 -07:00
1e297dbc76 Adding caco sprites. 2023-09-03 09:30:57 -07:00
dc240f851c Added TODO 2023-09-03 09:19:48 -07:00
63 changed files with 1032 additions and 8 deletions

View File

@ -15,7 +15,7 @@ TIMES as many layers of duct tape as the UAC's competing equivalent,
forming a sturdier, tougher seal than any so-called "actual
well-funded machine shop" could ever hope to achieve.
## Kiri's Gretchen Counter
## Gretchen Counter
![Gretchen Counter pickup sprite](sprites/gretchencounter/kgcpa0.png)
@ -53,7 +53,7 @@ The battery may be accessed (**Unload**/**Reload**) by removing the
rear battery bay panel. *May exhibit erratic behavior if used with a
low battery.*
## Kiri's Wiring Bypass Kit
## Wiring Bypass Kit
![Wiring Bypass Kit pickup sprite](sprites/jumpercables/jmpka0.png)
@ -118,5 +118,16 @@ Appendix D is unavailable in the Kiri's Discount Wiring Bypass Kit
manual. To obtain a copy of appendix D, please purchase a subscription
to Kiri's Discount Wiring Bypass Kit: Professional Edition.
## Cursed Cacodemon Plushie
![Caco plushie pickup sprite](sprites/cacoplush/cacoplushie_pickup.png)
Some random junk we found in the bin behind SnekTech HQ. Give it to
your kid or something as a gift.
Caution: May bite.
### How to use
Loadout code: kac

20
TODO.md Normal file
View File

@ -0,0 +1,20 @@
- ~~Integrate caco plush~~
- ~~Makefile for caco plush sprites~~
- ~~Fix upper/lowercase consistency between objects~~
- ~~Jumper cables~~
- ~~Jumper cables ammohandler stuff~~
- ~~Fix wiring kit brightmaps~~
- Caco plush
- ~~Caco plush spawnflags~~
- ~~Add caco plush to menu~~
- ~~Caco plush documentation~~
- ~~Brightmaps for caco plush~~
- ~~Set actual chance for caco plush spawn~~
- Caco plush sprite scaling
- Headers and comment formatting pass
- Organize source_data

View File

@ -42,21 +42,52 @@ brightmap sprite kgcpb0
brightmap sprite jmppa0
{
map "sprites/bright_jmpr_1.png"
map "sprites/jumpercables/bright_jmpr_1.png"
disablefullbright
}
brightmap sprite jmppb0
{
map "sprites/bright_jmpr_2.png"
map "sprites/jumpercables/bright_jmpr_2.png"
disablefullbright
}
brightmap sprite jmppc0
{
map "sprites/bright_jmpr_3.png"
map "sprites/jumpercables/bright_jmpr_3.png"
disablefullbright
}
// ----------------------------------------------------------------------
// Caco plushie brightmaps
brightmap sprite kcplb0
{
map "sprites/cacoplush/bright_cacoplushie_idle.png"
disablefullbright
}
brightmap sprite kcpld0
{
map "sprites/cacoplush/bright_cacoplushie_pickup.png"
disablefullbright
}
brightmap sprite kcple0
{
map "sprites/cacoplush/bright_cacoplushie_glowing1.png"
disablefullbright
}
brightmap sprite kcplf0
{
map "sprites/cacoplush/bright_cacoplushie_glowing2.png"
disablefullbright
}
brightmap sprite kcplg0
{
map "sprites/cacoplush/bright_cacoplushie_glowing3.png"
disablefullbright
}

View File

@ -9,5 +9,6 @@ OptionMenu "SnekTechMenu"
StaticText "----- Item Spawns -----", "Teal"
FlagOption "Gretchen Counter", "snektech_spawnflags", "YesNo", 0
FlagOption "Wiring Bypass Kit", "snektech_spawnflags", "YesNo", 1
FlagOption "Cacodemon Plushie", "snektech_spawnflags", "YesNo", 2
}

View File

@ -1,3 +1,5 @@
kiri/gretchencounter_click "sounds/kirigretchencounter_click.ogg"
kiri/gretchencounter_blip "sounds/kirigretchencounter_blip.ogg"
kiri/gretchencounter_onoff "sounds/kirigretchencounter_onoff.ogg"
kiri/cacoplushie_throw "sounds/kiri_caco_throw.ogg"
kiri/cacoplushie_despawn "sounds/kiri_caco_despawn.ogg"

Binary file not shown.

BIN
sounds/kiri_caco_throw.ogg Normal file

Binary file not shown.

View File

@ -16,7 +16,19 @@ all : \
../sprites/gretchencounter/kgcpb0.png \
../sprites/gretchencounter/bright_kgcpa0.png \
../sprites/gretchencounter/bright_kgcpb0.png \
../sprites/gretchencounter/bright_kgcmc0.png
../sprites/gretchencounter/bright_kgcmc0.png \
../sounds/kiri_caco_despawn.ogg \
../sounds/kiri_caco_throw.ogg \
../sprites/cacoplush/cacoplushie_idle.png \
../sprites/cacoplush/cacoplushie_glowing1.png \
../sprites/cacoplush/cacoplushie_glowing2.png \
../sprites/cacoplush/cacoplushie_glowing3.png \
../sprites/cacoplush/cacoplushie_pickup.png \
../sprites/cacoplush/bright_cacoplushie_idle.png \
../sprites/cacoplush/bright_cacoplushie_pickup.png \
../sprites/cacoplush/bright_cacoplushie_glowing1.png \
../sprites/cacoplush/bright_cacoplushie_glowing2.png \
../sprites/cacoplush/bright_cacoplushie_glowing3.png
# Base frames
../sprites/gretchencounter/kgcma0.png : gretchencounter_weaponsprite.aseprite
@ -353,3 +365,58 @@ brightmaps : povsprite.aseprite
rm tmp?.png
# ----------------------------------------------------------------------
# Caco plush
# Sounds
../sounds/kiri_caco_despawn.ogg : kiri_caco_despawn.wav
ffmpeg -i $^ "-filter:a" "volume=1.0" $@
../sounds/kiri_caco_throw.ogg : kiri_caco_throw.wav
ffmpeg -i $^ "-filter:a" "volume=1.0" $@
# Sprites
../sprites/cacoplush/cacoplushie_idle.png : cacoplushie.aseprite
aseprite -b $^ \
--ignore-layer "brightmap" \
--scale 0.77 \
--frame-range 0,0 --save-as $@
../sprites/cacoplush/cacoplushie_pickup.png : cacoplushie.aseprite
aseprite -b $^ \
--ignore-layer "brightmap" \
--frame-range 0,0 \
--scale 0.25 \
--save-as $@
../sprites/cacoplush/cacoplushie_glowing1.png \
../sprites/cacoplush/cacoplushie_glowing2.png \
../sprites/cacoplush/cacoplushie_glowing3.png : cacoplushie.aseprite
aseprite -b $^ \
--scale 0.77 \
--ignore-layer "brightmap" \
--frame-range 1,3 --save-as ../sprites/cacoplush/cacoplushie_glowing.png
# Brightmaps
../sprites/cacoplush/bright_cacoplushie_idle.png : cacoplushie.aseprite
aseprite -b $^ \
--layer "brightmap" \
--scale 0.77 \
--frame-range 0,0 --save-as $@
../sprites/cacoplush/bright_cacoplushie_pickup.png : cacoplushie.aseprite
aseprite -b $^ \
--layer "brightmap" \
--frame-range 0,0 \
--scale 0.25 \
--save-as $@
../sprites/cacoplush/bright_cacoplushie_glowing1.png \
../sprites/cacoplush/bright_cacoplushie_glowing2.png \
../sprites/cacoplush/bright_cacoplushie_glowing3.png : cacoplushie.aseprite
aseprite -b $^ \
--scale 0.77 \
--layer "brightmap" \
--frame-range 1,3 --save-as ../sprites/cacoplush/bright_cacoplushie_glowing.png

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 684 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 629 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 B

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 B

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 B

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 B

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 B

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 B

After

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 B

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 B

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 B

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 B

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 B

After

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 B

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 B

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 B

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 B

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 B

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 B

After

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 359 B

View File

@ -60,3 +60,39 @@ sprite JMPPC0, 320, 200 {
patch JMPPC0,0,0 { }
}
// ----------------------------------------------------------------------
// Caco plushie
// First-person view while holding.
Sprite "KCPLB0", 100, 83
{
Offset -120, -100
Patch "sprites/cacoplush/cacoplushie_idle.png", 0, 0
}
// Pickup sprite on the ground.
Sprite "KCPLD0", 32, 27
{
Offset 16, 13
Patch "sprites/cacoplush/cacoplushie_pickup.png", 0, 0
}
// Glowing frames.
Sprite "KCPLE0", 100, 83
{
Offset -120, -100
Patch "sprites/cacoplush/cacoplushie_glowing1.png", 0, 0
}
Sprite "KCPLF0", 100, 83
{
Offset -120, -100
Patch "sprites/cacoplush/cacoplushie_glowing2.png", 0, 0
}
Sprite "KCPLG0", 100, 83
{
Offset -120, -100
Patch "sprites/cacoplush/cacoplushie_glowing3.png", 0, 0
}

View File

@ -3,3 +3,4 @@ version "4.10"
#include "zscript/snektech.zs"
#include "zscript/jumpercables.zs"
#include "zscript/gretchencounter.zs"
#include "zscript/cacoplushie.zs"

812
zscript/cacoplushie.zs Normal file
View File

@ -0,0 +1,812 @@
// ----------------------------------------------------------------------
// Cursed Cacodemon Plushie
// ----------------------------------------------------------------------
//
// Scrawled on the tag is the text "Even in death, I will protect you,
// and not even the gods can stop me."
//
const HDLD_KIRI_CACOPLUSHIE = "kac";
const KIRI_CACOPLUSHIE_FAINTED_MONSTER_COOLDOWN = 60;
const KIRI_CACOPLUSHIE_CLASS = "Trilobite";
// For testing out shield mechanics, if we ever open this up to other
// monster types...
//
// const KIRI_CACOPLUSHIE_CLASS = "PainLord";
//
// Hey, want to see something totally broken? Spawn an imp healer and
// raise an army!
//
// const KIRI_CACOPLUSHIE_CLASS = "Regentipede";
const KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_HEALTH = 1;
const KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_SHIELD = 2;
const KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_NAME_INDEX = 3;
const KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_FAINTED = 4;
class KiriCacodemonPlushieProjectile : SlowProjectile
{
// Copied some necessary bits from the normal grenade.
default
{
mass 500;
scale 0.3;
+bright;
}
// What item threw this?
actor thrower;
states
{
spawn:
APBX BCDE 2 {
A_Trail();
} loop;
}
override void ExplodeSlowMissile(line blockingline, actor blockingobject)
{
spawnCritter(blockingobject);
destroy();
}
action void spawnCritter(actor beaned_actor)
{
KiriCacodemonPlushie plushie = KiriCacodemonPlushie(invoker.thrower);
if(plushie) {
plushie.spawnCritter(invoker, beaned_actor);
}
}
}
class KiriCacodemonPlushie : HDWeapon {
static const String KIRI_CACOPLUSHIE_NAMES[] = {
"<unnamed>",
"Billy",
"Jimmy",
"Bob",
"Kiddo",
"Lucky",
"Daisy",
"Murderface Skullslasher",
"Monica, Destroyer of Worlds",
"Fido",
"Thor",
"Loki",
"Taiyo",
"Fluffy",
"Leonardo",
"Donatello",
"Raphael",
"Michaelangelo",
"Winston",
"Egon",
"Ray",
"Peter",
"Zuul",
"Gozer",
"Slimer",
"Janine",
"Dana",
"Quark",
"Odo",
"Sisko",
"Jake",
"Nog",
"Rom",
"Kira",
"Jadzia",
"Ezri",
"Kor",
"Koloth",
"Kang",
"Garrack",
"Bashir",
"O'brien",
"Taco Works"
};
actor spawned_creature;
actor spawned_spawnball;
int fainted_monster_despawn_countdown;
default
{
+hdweapon.fitsinbackpack;
+INVENTORY.PERSISTENTPOWER;
+INVENTORY.INVBAR;
inventory.icon "KCPLD0";
inventory.pickupsound "misc/p_pkup";
inventory.pickupmessage "Picked up a cursed Cacodemon plushie.";
inventory.amount 1;
inventory.maxamount 1;
hdweapon.refid HDLD_KIRI_CACOPLUSHIE;
scale 0.63;
tag "Cacodemon Plushie";
}
states
{
spawn:
KCPL D 0 -1;
stop;
unload:
goto ready;
firemode:
goto ready;
altfire:
goto ready;
ready:
KCPL B 0 { A_WeaponReady(WRF_ALL); A_WeaponBusy(false); }
// Jump to idle and skip glowing anim if we have a monster or
// spawnball out.
KCPL B 0 A_JumpIf(!(invoker.spawned_spawnball || invoker.spawned_creature), 4);
// Glowing animation.
KCPL E 3;
KCPL F 3;
KCPL G 3;
KCPL G 0 A_Jump(255, 2); // Skip idle animation.
// Idle animation.
KCPL B 1;
KCPL B 0;
goto readyend;
waiting:
KCPL B 5;
KCPL B 0 A_Refire("waiting");
goto ready;
fire:
KCPL B 2 offset(0, 20);
KCPL B 2 offset(0, 40);
KCPL B 2 offset(0, 55) {
A_StartSound("kiri/cacoplushie_throw");
}
KCPL B 2 offset(0, 70);
KCPL B 0 {
if(!invoker.spawned_spawnball && !invoker.spawned_creature) {
// No spawn ball or monster. Throw a new one.
A_AlertMonsters();
bool success;
actor newcaconade;
[success, newcaconade] = A_SpawnItemEx(
"KiriCacodemonPlushieProjectile",
xofs : cos(invoker.owner.pitch) * -4,
yofs : 3, // Offset a little bit for a right handed throw.
zofs : invoker.owner.height * 0.88 - sin(invoker.owner.pitch) * -4,
xvel : cos(invoker.owner.pitch) * 20,
yvel : 0,
zvel : -sin(invoker.owner.pitch) * 20,
flags : SXF_NOCHECKPOSITION | SXF_TRANSFERPITCH);
if(success) {
KiriCacodemonPlushieProjectile newcaconade2 =
KiriCacodemonPlushieProjectile(newcaconade);
newcaconade2.thrower = invoker;
invoker.spawned_spawnball = newcaconade2;
invoker.fainted_monster_despawn_countdown =
KIRI_CACOPLUSHIE_FAINTED_MONSTER_COOLDOWN;
}
} else {
// Monster or spawn ball is already out. Recall it.
invoker.despawnMonster();
}
// Reset help text, now that the context has changed.
invoker.A_SetHelpText();
}
KCPL B 2 offset(0, 70);
KCPL B 2 offset(0, 55);
KCPL B 2 offset(0, 40);
KCPL B 2 offset(0, 20);
goto waiting;
reload:
KCPL B 2 offset(0, 20);
KCPL B 2 offset(0, 40);
KCPL B 2 offset(0, 55);
KCPL B 5 offset(0, 70);
KCPL B 0 { drinkBloodPack(); }
KCPL B 2 offset(0, 70);
KCPL B 2 offset(0, 55);
KCPL B 2 offset(0, 40);
KCPL B 2 offset(0, 20);
goto ready;
select0:
KCPL B 0 offset(0, 120);
---- B 1 A_Raise(12);
wait;
deselect0:
KCPL B 0;
---- B 1 A_Lower(12);
wait;
}
action void drinkBloodPack()
{
// Refuse to feed active monsters.
if(invoker.spawned_creature || invoker.spawned_spawnball) {
if(invoker.owner) {
invoker.owner.A_Log(
String.format(
"Cannot feed blood to %s when they're not in their plushie!",
invoker.getMonsterName()),
true);
}
return;
}
// Refuse to feed monsters that don't need any more.
if(invoker.getMonsterHealth() == invoker.getMonsterMaxHealth()) {
if(invoker.owner) {
invoker.owner.A_Log(
String.format(
"%s doesn't need any more food for now.",
invoker.getMonsterName()),
true);
}
return;
}
if(invoker.owner.countInv("SecondBlood")) {
// Consume a bloodbag and add health.
A_StartSound("potion/chug");
invoker.owner.A_TakeInventory("SecondBlood", 1, TIF_NOTAKEINFINITE);
invoker.setMonsterHealth(
invoker.getMonsterHealth() +
invoker.getMonsterMaxHealth() * 0.25);
// Throw out a spend blood bag.
A_SpawnItemEx(
"BloodBagWorn",
xofs : 0,
yofs : 0,
zofs : invoker.owner.height / 2.0,
xvel : frandom(0, 10),
yvel : frandom(-5, 5),
flags : SXF_NOCHECKPOSITION);
} else {
// No bloodbags available. Take health instead.
HDBleedingWound hbl = HDBleedingWound.inflict(
invoker.owner, 2, 1, source : invoker.owner);
HDPlayerPawn(invoker.owner).bloodloss += 0.1;
A_StartSound(invoker.owner.painsound);
invoker.setMonsterHealth(
invoker.getMonsterHealth() +
invoker.getMonsterMaxHealth() * 0.1);
}
}
// These are for allowing multiple copies of this weapon at the
// same time, but the only things on it that get stored are the
// weaponstatus, meaning that we lose the pointer to the spawned
// actor. So we have to despawn any actor we have out before
// switching.
//
// Yes, this means you only get one monster to play with at a
// time.
override bool AddSpareWeapon(
actor newowner)
{
return AddSpareWeaponRegular(newowner);
}
override hdweapon GetSpareWeapon(actor newowner, bool reverse, bool doselect)
{
despawnMonster();
return GetSpareWeaponRegular(newowner, reverse, doselect);
}
override void beginPlay()
{
super.beginPlay();
setRandomMonsterName();
setMonsterHealth(getMonsterMaxHealth());
setMonsterShield(getMonsterMaxShield());
}
int getMonsterHealth() const
{
return weaponstatus[KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_HEALTH];
}
int getMonsterMaxHealth() const
{
return getDefaultByType(KIRI_CACOPLUSHIE_CLASS).health;
}
void setMonsterHealth(int health)
{
if(health > getMonsterMaxHealth()) {
health = getMonsterMaxHealth();
}
weaponstatus[KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_HEALTH] = health;
if(health > getMonsterMaxHealth() / 2.0) {
setMonsterFainted(false);
}
}
int getMonsterShield() const
{
return weaponstatus[KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_SHIELD];
}
int getMonsterMaxShield() const
{
return getDefaultByType(KIRI_CACOPLUSHIE_CLASS).maxshields;
}
void setMonsterShield(int shield)
{
if(shield > getMonsterMaxShield()) {
shield = getMonsterMaxShield();
}
weaponstatus[KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_SHIELD] = shield;
}
override void DrawHUDStuff(HDStatusBar sb, HDWeapon hdw, HDPlayerPawn hpl)
{
KiriCacodemonPlushie plushie = KiriCacodemonPlushie(hdw);
String monsterName = getMonsterName();
int monsterHealth = getMonsterHealth();
int monsterShield = getMonsterShield();
int monsterFainted = getMonsterFainted();
if(plushie) {
// Draw the monster name. Use a green color if it's okay
// or a red color if it's fainted.
sb.drawstring(
sb.psmallfont,
monsterName, (-20,-30),
sb.DI_TEXT_ALIGN_RIGHT | sb.DI_TRANSLATABLE | sb.DI_SCREEN_CENTER_BOTTOM,
monsterFainted ? Font.CR_DARKRED : Font.CR_GREEN);
// Draw blood packs where we'd normally put ammo.
sb.drawimage(
"PBLDA0",
(-48,-10),
sb.DI_SCREEN_CENTER_BOTTOM,
scale : (0.75, 0.75));
sb.drawnum(
hpl.countinv("SecondBlood"),
-48, -8,
sb.DI_SCREEN_CENTER_BOTTOM);
// TODO: Draw something like blue potions or batteries for
// shields?
// Draw health.
sb.drawwepnum(
monsterHealth,
getMonsterMaxHealth());
// Draw shield.
sb.drawwepnum(
monsterShield,
getMonsterMaxShield(), posy: -10);
}
}
override double weaponbulk()
{
return 10;
}
void setRandomMonsterName()
{
weaponstatus[KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_NAME_INDEX] =
random(1, KiriCacodemonPlushie.KIRI_CACOPLUSHIE_NAMES.size() - 1);
A_SetHelpText();
}
String getMonsterName() const
{
return KiriCacodemonPlushie.KIRI_CACOPLUSHIE_NAMES[
weaponstatus[KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_NAME_INDEX]];
}
void despawnMonster()
{
// Whatever we despawn, play the despawn sound for it.
if(spawned_spawnball || spawned_creature) {
A_StartSound("kiri/cacoplushie_despawn");
}
// If we have a spawn projectile flying through the air,
// despawn that.
if(spawned_spawnball) {
spawned_spawnball.A_SpawnItemEx("TeleFog");
spawned_spawnball.destroy();
}
// If we have the creature in the world, despawn that.
if(spawned_creature) {
spawned_creature.A_SpawnItemEx("TeleFog");
spawned_creature.destroy();
if(owner) {
owner.A_Log(
String.format("%s has returned to the plushie.", getMonsterName()),
true);
}
}
// Adjust help text because the context changed. Now we can
// feed it and possibly deploy it.
A_SetHelpText();
}
void setMonsterFainted(bool fainted)
{
weaponstatus[KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_FAINTED] = fainted;
}
bool getMonsterFainted() const
{
return weaponstatus[KIRI_CACOPLUSHIE_WEAPONSTATUS_MONSTER_FAINTED];
}
override void DoEffect()
{
if(spawned_creature) {
// Check on shields.
HDMobBase mob = HDMobBase(spawned_creature);
if(mob) {
Actor shieldItemActor = mob.FindInventory("HDMagicShield");
HDMagicShield shieldItem = HDMagicShield(shieldItemActor);
if(shieldItem) {
// Counteract natural shield regeneration. We want
// to limit this to when the monster is asleep.
if(shieldItem.amount > getMonsterShield()) {
shieldItem.amount = getMonsterShield();
}
// Notify us if shields have dropped below what we
// last saw them at.
if(shieldItem.amount != getMonsterShield()) {
setMonsterShield(shieldItem.amount);
}
}
}
// Counteract monster health regeneration. We want to
// limit this to when the player feeds the monster.
if(spawned_creature.health > getMonsterHealth())
{
spawned_creature.health = getMonsterHealth();
}
// Notify us if the health has dropped.
if(spawned_creature.health != getMonsterHealth())
{
setMonsterHealth(spawned_creature.health);
}
// If the monster reaches zero health, then despawn it
// after a short delay. We still want the death animation
// to play out.
if(spawned_creature.health <= 0.0) {
setMonsterFainted(true);
// On the first frame of the monster being downed,
// show the message.
if(fainted_monster_despawn_countdown ==
KIRI_CACOPLUSHIE_FAINTED_MONSTER_COOLDOWN)
{
if(owner) {
owner.A_Log(String.format("%s has fainted!", getMonsterName()), true);
}
}
// After some number of frames, actually despawn the
// monster.
fainted_monster_despawn_countdown -= 1;
if(fainted_monster_despawn_countdown <= 0) {
despawnMonster();
}
}
// Spawn green balls.
if(spawned_creature) {
if(level.time & 1) {
double mrad = spawned_creature.radius * 0.3;
spawned_creature.A_SpawnParticle(
"green", SPF_FULLBRIGHT, 50,
frandom(4, 8), 0,
frandom(-mrad, mrad), frandom(-mrad, mrad), frandom(0.1, 0.9) * spawned_creature.height,
frandom(-0.2, 0.2), frandom(-0.2, 0.2), frandom(0.05, 0.2),
frandom(-0.05, 0.05), frandom(-0.05, 0.05), 0.06,
startalphaf : 0.8,
sizestep : 0.1);
}
}
} else {
// Monster is inside plushie. Sloooooooooooooowly
// regenerate shields, if the monster is awake.
if(!getMonsterFainted()) {
if(getMonsterShield() == -64) {
// Shields were totally broken. Extremely low chance
// to restore shields each frame.
if(frandom(0, 100) < 0.1) {
if(owner) {
owner.A_Log(
String.format(
"%s's shields have started charging.",
getMonsterName()),
true);
}
setMonsterShield(0);
}
} else {
// Shields are up and slowly recharging.
if(frandom(0, 100) < 5) {
setMonsterShield(getMonsterShield() + 1);
}
}
}
}
}
override void consolidate()
{
// Restore shields on level exit.
if(!getMonsterFainted()) {
setMonsterShield(getMonsterMaxShield());
}
A_SetHelpText();
}
action void spawnCritter(actor spawn_ball, actor beaned_actor)
{
bool success = false;
// Handle monsters that have already fainted. They won't
// spawn.
if(invoker.getMonsterFainted())
{
if(invoker.owner) {
invoker.owner.A_Log(
String.format(
"%s has fainted and cannot manifest yet.",
invoker.getMonsterName()),
true);
}
spawn_ball.A_SpawnItemEx("TeleFog");
return;
}
int tries = 0;
// Offset to spawn at. Absolute directions, not relative.
float offset_x = 0;
float offset_y = 0;
float offset_z = 0;
// If we hit an enemy in the face, back off of that enemy by
// the sum of us and their radius. We already know we can't
// spawn inside of it.
if(beaned_actor) {
offset_x -= cos(spawn_ball.angle) * (beaned_actor.radius + 30);
offset_y -= sin(spawn_ball.angle) * (beaned_actor.radius + 30);
}
int extra_flags = 0; // FIXME: Remove this.
actor spawned_thing;
// Cumulative random offset we'll use to jitter the location a
// bit in the hopes of finding a valid spot.
float random_offset_x = 0;
float random_offset_y = 0;
// We need to give this a LOT of leeway as far as where we can
// spawn. The monsters are quite large compared to the
// projectile, meaning it can easily get into places that the
// monsters can't spawn into, so we need to aggressively
// search for a nearby spot to spawn into.
while(tries < 30 && !success) {
// This will add some jitter that will thrash
// back-and-forth on both X and Y axes, allowing us to
// gradually cover a bigger and bigger potential area.
float jitter_amount = 1;
if(frandom(0, 1) <= 0.5) {
random_offset_x = -abs(random_offset_x) - frandom(0, jitter_amount);
} else {
random_offset_x = abs(random_offset_x) + frandom(0, jitter_amount);
}
if(frandom(0, 1) <= 0.5) {
random_offset_y = -abs(random_offset_y) - frandom(0, jitter_amount);
} else {
random_offset_y = abs(random_offset_y) + frandom(0, jitter_amount);
}
// This will move the offset back a bit, away from the
// point of contact, and back towards the player, while
// also mixing in the jitter we calculated.
offset_x -= (spawn_ball.vel.x * 0.3) + random_offset_x;
offset_y -= (spawn_ball.vel.y * 0.3) + random_offset_y;
// Z axis is a little different. We already know the
// optimal height to spawn at would be the center of the
// sector, vertically, so nudge us, on that axis, towards
// the center of the sector. No need for jitter.
float sector_center_z =
(spawn_ball.ceilingz + spawn_ball.floorz) / 2.0;
if(spawn_ball.pos.z + offset_z > sector_center_z) {
offset_z -= 2.0;
} else if(spawn_ball.pos.z + offset_z < sector_center_z) {
offset_z += 2.0;
}
tries += 1;
// Spawn debug particles showing all the points we've
// tried.
if(hd_debug > 1) {
spawn_ball.A_SpawnParticle(
"yellow",
flags : 0,
size : 12,
xoff : offset_x,
yoff : offset_y,
zoff : offset_z);
}
// Attempt to spawn the monster.
[success, spawned_thing] = spawn_ball.A_SpawnItemEx(
KIRI_CACOPLUSHIE_CLASS,
xofs : offset_x,
yofs : offset_y,
zofs : offset_z,
xvel : 0,
yvel : 0,
zvel : 0,
angle : 0,
flags : SXF_SETMASTER | SXF_ABSOLUTEPOSITION | extra_flags);
if(success) {
// Spawn succeeded. Stop searching.
break;
}
}
if(success) {
// Copy health over.
spawned_thing.health = invoker.getMonsterHealth();
// Copy shields over (if applicable).
Actor shieldItemActor = spawned_thing.FindInventory("HDMagicShield");
HDMagicShield shieldItem = HDMagicShield(shieldItemActor);
if(shieldItem) {
shieldItem.amount = invoker.getMonsterShield();
}
// Set the name.
spawned_thing.setTag(invoker.getMonsterName());
// FIXME: Would prefer something to make the monster more
// distinct. Other attempts were made to distinguish the
// monster...
//
// This one was good because it made the monsters very
// distinct (built-in GZDoom translation):
//
// spawned_thing.settranslation("Ice")
// spawned_thing.translation = TRANSLATION_ICE;
//
// This would make it render in the transparent style of
// the ghosts (minus the coloration):
//
// spawned_thing.A_SetRenderStyle(1.0, STYLE_ADD);
//
// This one just made it all purple. Certainly looks
// distinct!
//
// spawned_thing.A_SetTranslation("AllPurple");
//
// This would just copy the player colors over.
//
// spawned_thing.translation = invoker.owner.translation;
// Spawn flash.
spawn_ball.A_SpawnItemEx(
"TeleFog",
xofs : offset_x,
yofs : offset_y,
zofs : offset_z,
flags : SXF_ABSOLUTEPOSITION);
// Keep track of the spawned creature.
invoker.spawned_creature = spawned_thing;
// If we smacked a mob in the face, then make that the
// spawned monster's target immediately.
if(beaned_actor) {
if(beaned_actor is "HDMobBase") {
spawned_thing.target = beaned_actor;
}
}
} else {
// Tell the player we failed.
if(invoker.owner) {
invoker.owner.A_Log(
String.format("Failed to summon %s: Not enough room.", invoker.getMonsterName()),
true);
}
}
}
override String, double getPickupSprite(bool usespare)
{
return "KCPLD0";
}
override String getHelpText()
{
if(spawned_creature || spawned_spawnball) {
return WEPHELP_FIRE.." Return "..getMonsterName().." to the plushie.\n";
}
return
WEPHELP_FIRE .. " Summon "..getMonsterName()..".\n"..
WEPHELP_RELOAD .. " Feed blood to "..getMonsterName()..".\n";
}
}

View File

@ -1,3 +1,16 @@
// ----------------------------------------------------------------------
// Gretchen Counter
// ----------------------------------------------------------------------
//
// This object clearly looks like it was recycled from a very old
// Geiger counter.
//
// Smells kind of like rotting meat.
//
// Try not to think about it.
//
#include "zscript/snektech.zs"
const HDLD_KIRI_GRETCHENCOUNTER = "kgc";

View File

@ -1,3 +1,14 @@
// ----------------------------------------------------------------------
// Wiring Bypass Kit
// ----------------------------------------------------------------------
//
// This is just a pile of electronics maintenance equipment. The usual
// stuff is here, like electrical tape, wire cutters, etc.
//
// There's also a meter that looks like it was made out of some beat
// up piece of equipment, and held together with duct tape.
//
const HDLD_KIRI_JUMPERCABLES = "jmp";
const jumperCablesRaycastRange = 48; // Same range as the DERP.

View File

@ -1,7 +1,15 @@
// ----------------------------------------------------------------------
// Snektech
// ----------------------------------------------------------------------
//
// Spawn flags for SnekTech items handled here.
//
enum SnekTechSpawnFlags
{
SNEKTECH_GRETCHENCOUNTER = 0,
SNEKTECH_JUMPERCABLES = 1
SNEKTECH_JUMPERCABLES = 1,
SNEKTECH_CACOPLUSHIE = 2
}
class SnekTechEventHandler : EventHandler
@ -54,6 +62,7 @@ class SnekTechEventHandler : EventHandler
HDBattery bat = HDBattery(e.Thing);
if(bat) {
bat.ItemsThatUseThis.Push("GretchenCounter");
bat.ItemsThatUseThis.Push("JumperCablesUsable");
}
// Delete disabled items from backpacks that just spawned.
@ -66,6 +75,11 @@ class SnekTechEventHandler : EventHandler
e.Thing,
SNEKTECH_JUMPERCABLES,
"JumperCablesUsable");
DoSnekTechBackpackSpawnCheck(
e.Thing,
SNEKTECH_CACOPLUSHIE,
"KiriCacodemonPlushie");
}
override void CheckReplacement(ReplaceEvent e)
@ -86,7 +100,12 @@ class SnekTechEventHandler : EventHandler
DoSnekTechReplacement(
SNEKTECH_JUMPERCABLES,
"Allmap", "JumperCablesUsable",
256, e);
12, e);
DoSnekTechReplacement(
SNEKTECH_CACOPLUSHIE,
"BlurSphere", "KiriCacodemonPlushie",
6, e);
}
}