HDSnekTechDiscountPartyPack/zscript/spraycan.zs
emmie 56a46d0790 spraycan: use permanent decals
instead of using auto decals, letting them time out and just Not
replacing them when they're meant to be gone, this spawns a permanent
decal (not affected by decal limit) and explicitly deletes it when
necessary.
as a consequence of this, spraying more than 11 decals Immediately
removes the oldest spray.

Addresses issue #1
2023-09-11 20:47:55 -07:00

562 lines
15 KiB
Plaintext

// ----------------------------------------------------------------------
// Spraypaint cans
// ----------------------------------------------------------------------
//
// Super cheap.
//
const KIRI_SPRAY_DISTANCE = 96;
const KIRI_SPRAY_SHAKEANIM_MAXANGLE = 20.0;
const KIRI_SPRAY_MAXPAINT = 50;
const KIRI_SPRAY_MAXPRESSURE = 100;
const KIRI_SPRAY_PRESSUREBUILDSCALE = 20;
const KIRI_SPRAY_PRESSURE_PER_USE = (KIRI_SPRAY_MAXPRESSURE / 2);
const KIRI_SPRAY_PRESSURE_DECAY_RATE = 0.1;
const HDLD_KIRI_SPRAYCAN = "ksp";
enum KiriSprayerStatus
{
KIRI_SPRAY_WS_PAINT = 1,
KIRI_SPRAY_WS_PRESSURE = 2
}
class SnekTechSprayer : HDWeapon
{
default
{
radius 2;
height 4;
+SpriteAngle;
Scale 0.4;
+hdweapon.fitsinbackpack;
+weapon.wimpy_weapon;
+INVENTORY.PERSISTENTPOWER;
+INVENTORY.INVBAR;
// inventory.icon "KSPRB0";
inventory.pickupsound "kiri/spraycan_rattle";
inventory.pickupmessage "Picked up some spraypaint cans.";
inventory.amount 1;
inventory.maxamount 100;
hdweapon.refid HDLD_KIRI_SPRAYCAN;
tag "Spraypaint Can";
}
states
{
spawn:
KSPR B -1;
stop;
ready:
KSPR A 1 {
A_WeaponReady(WRF_ALL);
A_WeaponBusy(false);
}
goto readyend;
altfire:
reload:
KSPR A 1 offset(0, 20) {
A_OverlayPivot(0, 0.5, 1.0);
A_OverlayRotate(0, 0);
A_StartSound(
"kiri/spraycan_rattle",
CHAN_WEAPON, CHANF_NOSTOP);
}
KSPR A 1 offset(0, 40) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 1.0/3.0); }
KSPR A 1 offset(0, 60) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 2.0/3.0); }
KSPR A 1 offset(0, 80) {
A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE);
// Taper off pressure increase amount based on how much
// pressure is in there already.
float pressureIncreaseScale = 1.0 - (
float(invoker.GetPressure()) /
float(KIRI_SPRAY_MAXPRESSURE));
pressureIncreaseScale *= pressureIncreaseScale;
// Add pressure.
int pressure = invoker.GetPressure();
pressure +=
random(0,
KIRI_SPRAY_PRESSUREBUILDSCALE
* invoker.weaponstatus[KIRI_SPRAY_WS_PAINT]
/ KIRI_SPRAY_MAXPAINT);
// Cap pressure amount.
if(pressure > KIRI_SPRAY_MAXPRESSURE) {
pressure = KIRI_SPRAY_MAXPRESSURE;
}
invoker.SetPressure(pressure);
}
KSPR A 1 offset(0, 60) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 2.0/3.0); }
KSPR A 1 offset(0, 40) { A_OverlayRotate(0, sin(level.time * 5.0) * KIRI_SPRAY_SHAKEANIM_MAXANGLE * 1.0/3.0); }
KSPR A 1 offset(0, 20) { A_OverlayRotate(0, 0); }
goto ready;
fire:
KSPR A 2 {
if(invoker.GetPressure() < KIRI_SPRAY_PRESSURE_PER_USE) {
invoker.owner.A_Log("Not enough pressure to spray.", true);
} else {
float zOffset = GunHeight() * 0.8;
FLineTraceData lineTraceData;
class<SnekTechSprayerPattern> sprayerPatternClass = "SnekTechSprayerPattern";
// Find the spray pattern class that matches the
// player's CVar for selected pattern.
String currentSprayCVar = CVar.GetCVar(
"snektech_spraypattern",
invoker.owner.player).GetString();
for(int i = 0; i < AllActorClasses.size(); i++) {
class<SnekTechSprayerPattern> thisPatternClass = (class<SnekTechSprayerPattern>)(AllActorClasses[i]);
if(thisPatternClass) {
if(thisPatternClass.GetClassName() == currentSprayCVar) {
sprayerPatternClass = (class<SnekTechSprayerPattern>)(AllActorClasses[i]);
break;
}
}
}
// Get the actual decal name out of it.
let defaultSprayClass = GetDefaultByType(sprayerPatternClass);
String actualDecalName = defaultSprayClass.decalName;
// Find a wall to spray on.
bool traceHit = LineTrace(
angle,
KIRI_SPRAY_DISTANCE,
pitch,
flags : TRF_THRUACTORS,
offsetz : zOffset,
data : lineTraceData);
if(traceHit && lineTraceData.HitLine) {
A_StartSound(
"kiri/spraycan_spray",
CHAN_WEAPON);
let normal = lineTraceData.HitDir;
let spawnedThing = Actor.Spawn("SnekTechSprayerDecalSpawner", lineTraceData.HitLocation - normal);
SnekTechSprayerDecalSpawner spawner = SnekTechSprayerDecalSpawner(spawnedThing);
if(spawner) {
spawner.angle = VectorAngle(-normal.x, -normal.y);
spawner.actualDecalName = actualDecalName;
spawner.master = invoker.owner;
invoker.SetPressure(invoker.GetPressure() - KIRI_SPRAY_PRESSURE_PER_USE);
invoker.weaponstatus[KIRI_SPRAY_WS_PAINT] -= 1;
}
} else {
invoker.owner.A_Log("Not close enough to a wall to spray.", true);
}
}
}
goto waiting;
firemode:
KSPR A 2 { invoker.CycleSprayPattern(); }
goto waiting;
waiting:
KSPR A 5;
KSPR A 0 A_Refire("waiting");
goto ready;
select0:
KSPR A 0 offset(0, 120);
---- A 1 A_Raise(12);
wait;
deselect0:
KSPR A 0;
---- A 1 A_Lower(12);
wait;
}
override bool AddSpareWeapon(actor newowner)
{
return AddSpareWeaponRegular(newowner);
}
override HDWeapon GetSpareWeapon(actor newowner, bool reverse, bool doselect)
{
return GetSpareWeaponRegular(newowner, reverse, doselect);
}
// Why do we store pressure as this weird equation?
//
// Pressure is stored as a time since the a hypothetical last
// "fully charged" time, based on the level time and pressure
// decay rate.
//
// This way we can decay pressure without ticking, important for
// spare weapons and stuff in backpacks.
int GetPressure() const
{
int time = level.TotalTime;
int lastChargedTime = weaponstatus[KIRI_SPRAY_WS_PRESSURE];
int pressure = int(
float(KIRI_SPRAY_MAXPRESSURE) -
float(time - lastChargedTime) * KIRI_SPRAY_PRESSURE_DECAY_RATE);
if(pressure > KIRI_SPRAY_MAXPRESSURE) {
pressure = KIRI_SPRAY_MAXPRESSURE;
}
if(pressure < 0) {
pressure = 0;
}
return pressure;
}
void SetPressure(int pressure)
{
if(pressure > KIRI_SPRAY_MAXPRESSURE) {
pressure = KIRI_SPRAY_MAXPRESSURE;
}
if(pressure < 0) {
pressure = 0;
}
int time = level.TotalTime;
int lastChargedTime = int(round(
float(pressure - KIRI_SPRAY_MAXPRESSURE) / float(KIRI_SPRAY_PRESSURE_DECAY_RATE) + time));
weaponstatus[KIRI_SPRAY_WS_PRESSURE] = lastChargedTime;
}
void ResetPressure()
{
int resetValue = -int(float(KIRI_SPRAY_MAXPRESSURE) / float(KIRI_SPRAY_PRESSURE_DECAY_RATE));
weaponstatus[KIRI_SPRAY_WS_PRESSURE] = resetValue;
}
override void Consolidate()
{
ResetPressure();
}
override void InitializeWepStats(bool idfa)
{
super.InitializeWepStats(idfa);
SetPressure(0);
// Add a little bit of randomness to the amount of paint in
// the can.
weaponstatus[KIRI_SPRAY_WS_PAINT] =
KIRI_SPRAY_MAXPAINT
- random(0, KIRI_SPRAY_MAXPAINT / 10);
}
override void DrawHUDStuff(
HDStatusBar statusBar, HDWeapon weapon,
HDPlayerPawn pawn)
{
// Current pressure (top bar).
statusBar.DrawWepNum(
GetPressure(),
KIRI_SPRAY_MAXPRESSURE, posy:-10);
// Total paint remaining (bottom bar).
statusBar.DrawWepNum(
weaponstatus[KIRI_SPRAY_WS_PAINT],
KIRI_SPRAY_MAXPAINT);
}
void GetSprayPatternList(Array<String> ret)
{
ret.clear();
for(int i = 0; i < AllActorClasses.Size(); i++) {
class<Actor> c = AllActorClasses[i];
if(c is "SnekTechSprayerPattern" && c != "SnekTechSprayerPattern") {
ret.push(c.GetClassName());
}
}
}
void CycleSprayPattern()
{
// Find the current entry in the list of patterns.
String currentPattern = CVar.GetCVar("snektech_spraypattern", owner.player).GetString();
Array<String> patternList;
GetSprayPatternList(patternList);
int currentIndex = patternList.find(currentPattern);
// Make sure we're actually in the list (CVar might not be in
// list, due to mods getting shuffled around and whatever).
if(currentIndex >= patternList.size()) {
currentIndex = -1;
}
// Increment the index.
int newIndex = (currentIndex + 1) % patternList.size();
// Set the new CVar.
currentPattern = patternList[newIndex];
// first, update the Actual cvar
if (owner.PlayerNumber() == consoleplayer)
CVar.FindCVar("snektech_spraypattern").SetString(currentPattern);
// then update the per-player copy so the help text is correct
CVar.GetCVar("snektech_spraypattern", owner.player).SetString(currentPattern);
owner.A_Log("Selected spray pattern: "..currentPattern, true);
// Current pattern is referenced in the help text, so reset
// it.
A_SetHelpText();
}
override double WeaponBulk()
{
return 10;
}
override String GetHelpText()
{
super.GetHelpText();
int messageIndex = level.time;
Array<String> messages = {
"Deface the tyrant's property.",
"Engage in civil disobedience.",
"Create a beautiful work of art.",
"Show the world that you still exist.",
"Defy the tyrant."
};
String currentPattern = CVar.GetCVar("snektech_spraypattern", owner.player).GetString();
return
WEPHELP_FIREMODE .. " Cycle pattern (current: "..currentPattern..").\n"..
WEPHELP_ALTFIRE .. " Shake the can.\n"..
WEPHELP_FIRE .. " "..messages[messageIndex % messages.Size()].."\n";
}
}
class SnekTechSprayerDecalSpawner : Decal
{
default
{
+nogravity;
}
states
{
spawn:
TNT1 A -1;
stop;
}
int thisSprayerId;
String actualDecalName;
Array<Thinker> decals;
void SpawnDecal()
{
Thinker think;
array<thinker> seen;
// find every decal that Isn't ours
let iter = ThinkerIterator.Create('Thinker', STAT_DECAL);
while (think = iter.Next()) {
// BaseDecal isn't exported to zscript so we have to filter this manually
if (think.GetClassName() == 'BaseDecal') {
seen.Push(think);
}
}
// pass the decal name as a map editor type of argument
args[0] = -int(name(actualDecalName));
// spawn the decal
Super.SpawnDecal();
// then find every decal that we spawned
iter.Reinit();
while (think = iter.Next()) {
if (think.GetClassName() == 'BaseDecal' &&
seen.Find(think) == seen.Size())
{
decals.Push(think);
}
}
}
override void OnDestroy()
{
for (let i = 0; i < decals.Size(); i++) {
if (decals[i]) {
decals[i].Destroy();
}
}
}
// Decal::BeginPlay deletes the actor
override void BeginPlay() {Actor.BeginPlay();}
override void PostBeginPlay()
{
Super.PostBeginPlay();
// copy A_SprayDecal z offset
setz(pos.z + height / 2);
SpawnDecal();
if (decals.Size() == 0) {
Destroy();
return;
}
// Figure out a new ID number.
ThinkerIterator iter = ThinkerIterator.Create("SnekTechSprayerDecalSpawner");
SnekTechSprayerDecalSpawner otherSpawner;
int maxId = 0;
while(otherSpawner = SnekTechSprayerDecalSpawner(iter.Next())) {
if(otherSpawner == self) {
continue;
}
// Keep track of the highest ID.
if(otherSpawner.thisSprayerId > maxId) {
if(otherSpawner.master == master) {
maxId = otherSpawner.thisSprayerId;
}
}
}
thisSprayerId = maxId + 1;
// Clear old sprayers.
iter = ThinkerIterator.Create("SnekTechSprayerDecalSpawner");
while(otherSpawner = SnekTechSprayerDecalSpawner(iter.Next())) {
if(otherSpawner == self) {
continue;
}
int maxSpraysPerPlayer =
CVar.GetCVar("snektech_maxspraysperplayer").GetInt();
if(otherSpawner.thisSprayerId <= (thisSprayerId - maxSpraysPerPlayer)) {
if(otherSpawner.master == master) {
otherSpawner.Destroy();
}
}
}
}
}
class SnekTechSprayerPattern : Actor
{
string decalName;
property decalName:decalName;
default {
SnekTechSprayerPattern.decalName "KiriTestDecal";
}
}
class SnekSpray_TransPride : SnekTechSprayerPattern
{
default
{
SnekTechSprayerPattern.decalName "SnekSpray_TransPride";
}
}
class SnekSpray_LesbianPride : SnekTechSprayerPattern
{
default
{
SnekTechSprayerPattern.decalName "SnekSpray_LesbianPride";
}
}
class SnekSpray_NBPride : SnekTechSprayerPattern
{
default
{
SnekTechSprayerPattern.decalName "SnekSpray_NBPride";
}
}
class SnekSpray_AcePride : SnekTechSprayerPattern
{
default
{
SnekTechSprayerPattern.decalName "SnekSpray_AcePride";
}
}
class SnekSpray_ProgressPride : SnekTechSprayerPattern
{
default
{
SnekTechSprayerPattern.decalName "SnekSpray_ProgressPride";
}
}
class SnekSpray_DemiPride : SnekTechSprayerPattern
{
default
{
SnekTechSprayerPattern.decalName "SnekSpray_DemiPride";
}
}
class SnekSpray_PanPride : SnekTechSprayerPattern
{
default
{
SnekTechSprayerPattern.decalName "SnekSpray_PanPride";
}
}
class SnekSpray_SwitchPride : SnekTechSprayerPattern
{
default
{
SnekTechSprayerPattern.decalName "SnekSpray_SwitchPride";
}
}
class SnekSpray_BiPride : SnekTechSprayerPattern
{
default
{
SnekTechSprayerPattern.decalName "SnekSpray_BiPride";
}
}
class SnekSpray_GayPride : SnekTechSprayerPattern
{
default
{
SnekTechSprayerPattern.decalName "SnekSpray_GayPride";
}
}