f4bf7a793e
GetCVar returns a per-player copy of the requested cvar, which is only used to have separate values for each player - changes are propagated To it, but not From it. writing to this cvar is only useful if you know what the actual new value is going to be & want the cvar to update Immediately. (in this case, the cvar needs to be updated before A_SetHelpText so the displayed spray name is correct) FindCVar returns the actual cvar for the local player. changes to this cvar Are propagated to other clients.
528 lines
14 KiB
Plaintext
528 lines
14 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 {
|
|
|
|
Actor spawnedThing;
|
|
bool success;
|
|
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);
|
|
|
|
[success, spawnedThing] = A_SpawnItemEx(
|
|
"SnekTechSprayerDecalSpawner",
|
|
xofs : 0, yofs : 0, zofs : zOffset);
|
|
|
|
SnekTechSprayerDecalSpawner spawner = SnekTechSprayerDecalSpawner(spawnedThing);
|
|
|
|
if(success && spawner) {
|
|
spawner.A_SetPitch(pitch);
|
|
spawner.A_SetAngle(angle);
|
|
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 : Actor
|
|
{
|
|
default
|
|
{
|
|
+nogravity;
|
|
}
|
|
|
|
states
|
|
{
|
|
spawn:
|
|
TNT1 A -1;
|
|
stop;
|
|
}
|
|
|
|
int timeSinceLastSpray;
|
|
int thisSprayerId;
|
|
String actualDecalName;
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_SprayDecal(actualDecalName, KIRI_SPRAY_DISTANCE);
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
super.Tick();
|
|
|
|
timeSinceLastSpray++;
|
|
|
|
if(timeSinceLastSpray >= 35 * 60) {
|
|
A_SprayDecal(actualDecalName, KIRI_SPRAY_DISTANCE);
|
|
timeSinceLastSpray = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
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";
|
|
}
|
|
}
|
|
|