More caco plushie work.
@ -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"
|
||||
|
@ -22,8 +22,8 @@ all : \
|
||||
../sprites/cacoplush/cacoplushie_idle.png \
|
||||
../sprites/cacoplush/cacoplushie_glowing1.png \
|
||||
../sprites/cacoplush/cacoplushie_glowing2.png \
|
||||
../sprites/cacoplush/cacoplushie_glowing3.png
|
||||
|
||||
../sprites/cacoplush/cacoplushie_glowing3.png \
|
||||
../sprites/cacoplush/cacoplushie_pickup.png
|
||||
|
||||
# Base frames
|
||||
../sprites/gretchencounter/kgcma0.png : gretchencounter_weaponsprite.aseprite
|
||||
@ -366,19 +366,29 @@ brightmaps : povsprite.aseprite
|
||||
# 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
|
||||
|
||||
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
sprites/cacoplush/cacoplushie_pickup.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
36
textures.txt
@ -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
|
||||
}
|
||||
|
||||
|
@ -3,3 +3,4 @@ version "4.10"
|
||||
#include "zscript/snektech.zs"
|
||||
#include "zscript/jumpercables.zs"
|
||||
#include "zscript/gretchencounter.zs"
|
||||
#include "zscript/cacoplushie.zs"
|
||||
|
795
zscript/cacoplushie.zs
Normal file
@ -0,0 +1,795 @@
|
||||
// ----------------------------------------------------------------------
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
} 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();
|
||||
}
|
||||
|
||||
// Copy player colors over.
|
||||
spawned_thing.translation = invoker.owner.translation;
|
||||
|
||||
// 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");
|
||||
|
||||
// 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";
|
||||
}
|
||||
}
|