+ add aesthetic fixes for small hud font to make it consistent with the big hud font

+ add option to add a projectile to timon's axe (splits mana use)
+ add option to speed up sapphire wand
+ make pickup flashes bright
+ make sapphire wand's trail look more like the explosion
+ add option to display a detailed quest log on the automap
+ make ettins and centaurs not play a hit sound when missing
+ add an option to set the behaviour of centaurs to make them more vulnerable
+ add an option to make players shatter frozen enemies on touch
+ add an option to change the sapphire wand's damage function
+ make sapphire wand's projectile appear at the proper height
+ add an option to change the frost spell's damage function
+ add an option to make bloodscourge's projectiles foil invulnerability
This commit is contained in:
alison wright 2022-07-10 07:24:30 -06:00
commit 6928c2f898
17 changed files with 614 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
gzdoom-crash.log

8
cvarinfo.txt Normal file
View File

@ -0,0 +1,8 @@
server bool vht_faxe_projectile = true;
server int vht_mwand_damagefunc = 0;
server int vht_mwand_speed = 1;
server int vht_mfrost_damagefunc = 1;
server bool vht_mbloodscourge_foilinvul = false;
server bool vht_player_touchshatter = true;
server bool vht_player_questlog = true;
server int vht_monster_centaur = 2;

BIN
graphics/SMALLIN1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

BIN
graphics/SMALLIN4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

BIN
graphics/SMALLIN7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

61
language.txt Normal file
View File

@ -0,0 +1,61 @@
[en default]
OPTMNU_VHT = "Vanilla Hexen Tweaks";
OB_VHTFWEAPAXE = "%o rode the axe-lightning from %k.";
VHT_FAXE_PROJECTILE = "Timon's Axe Also Fires a Projectile";
VHT_MWAND_DAMAGEFUNC = "Wand Damage Function";
VHT_MWAND_SPEED = "Wand Speed Increase";
VHT_MFROST_DAMAGEFUNC = "Frost Shards Damage Function";
VHT_MBLOODSCOURGE_FOILINVUL = "Bloodscourge Foils Invulnerability";
VHT_PLAYER_TOUCHSHATTER = "Shatter Frozen Enemies On Touch";
VHT_PLAYER_QUESTLOG = "Enable Quest Log";
VHT_MONSTER_CENTAUR = "Centaur Behaviour";
VHT_OPT_CONSTANT = "Constant";
VHT_OPT_MODIFIED = "Modified";
VHT_OPT_VANILLA = "Vanilla";
VHT_OPT_NOREFLECT = "No Projectile Reflection";
VHT_OPT_ANGLED = "No Reflection, Back is Vulnerable";
VHT_QST_1_0 =
"@ must find the emerald key\n"
"and get through the stronghold.\n"
"@' note says, 'it lays beyond\n"
"a stained warrior.'";
VHT_QST_1_1 =
"the chapel stands tall and\n"
"proud in the name of sin,\n"
"but its doors stay locked.\n"
"@ must find the silver key.";
VHT_QST_1_2 =
"@ need to ring the chapel's bell,\n"
"to call for the doom about\n"
"to befall cronos, and to build\n"
"a path forward.";
VHT_QST_1_3 =
"now, @ must find @' way to\n"
"the first step -- the seven portals.\n"
"\cr@ will not be able to return.";
VHT_QST_2_0 =
"the first step on @' journey\n"
"shall be to get through the guardians\n"
"which seal the realm beyond.\n"
"\n"
"the portals once meant to defend cronos\n"
"from invasion now merely keep @= from\n"
"delivering justice unto it.";
VHT_QST_2_1 =
"with the guardian of ice opened,\n"
"two more paths become available to @=:\n"
"- guardian of fire: %i/1\n"
"- guardian of steel: %i/2";
VHT_QST_3_0 =
"all that seems to be left here is\n"
"a switch, and some portals... perhaps\n"
"all @ need is to pass through here for now.";
VHT_QST_3_1 =
"there doesn't seem to be anything here\n"
"for @= right now.";

4
mapinfo.txt Normal file
View File

@ -0,0 +1,4 @@
GameInfo {
PlayerClasses = "VhtFighterPlayer", "VhtClericPlayer", "VhtMagePlayer"
AddEventHandlers = "VhtEvents"
}

35
menudef.txt Normal file
View File

@ -0,0 +1,35 @@
OptionValue "VhtDamageFunc" {
0, "$VHT_OPT_VANILLA"
1, "$VHT_OPT_MODIFIED"
2, "$VHT_OPT_CONSTANT"
}
OptionValue "VhtOnOff" {
0, "$VHT_OPT_VANILLA"
1, "$VHT_OPT_MODIFIED"
}
OptionValue "VhtCentaurBehaviour" {
0, "$VHT_OPT_VANILLA"
1, "$VHT_OPT_NOREFLECT"
2, "$VHT_OPT_ANGLED"
}
AddOptionMenu "OptionsMenu" {
SubMenu "$OPTMNU_VHT", "VhtMenu"
}
OptionMenu "VhtMenu" {
Title "$OPTMNU_VHT"
StaticText "Weapons"
Option "$VHT_FAXE_PROJECTILE", "vht_faxe_projectile", "OnOff"
Option "$VHT_MWAND_DAMAGEFUNC", "vht_mwand_damagefunc", "VhtDamageFunc"
ScaleSlider "$VHT_MWAND_SPEED", "vht_mwand_speed", 0, 6, 1, "$VHT_OPT_VANILLA"
Option "$VHT_MFROST_DAMAGEFUNC", "vht_mfrost_damagefunc", "VhtDamageFunc"
Option "$VHT_MBLOODSCOURGE_FOILINVUL", "vht_mbloodscourge_foilinvul", "OnOff"
StaticText "Players"
Option "$VHT_PLAYER_TOUCHSHATTER", "vht_player_touchshatter", "OnOff"
Option "$VHT_PLAYER_QUESTLOG", "vht_player_questlog", "OnOff"
StaticText "Monsters"
Option "$VHT_MONSTER_CENTAUR", "vht_monster_centaur", "VhtCentaurBehaviour"
}

69
vhtzs/events.zsc Normal file
View File

@ -0,0 +1,69 @@
class VhtEvents : StaticEventHandler {
bool m_useQuestLog;
string m_questLog;
VhtFnPlayerInv m_fGetQuests;
VhtQuestHolder vhtGetQuests() const {
return m_fGetQuests.vhtRun() ? VhtQuestHolder(m_fGetQuests.m_result) : null;
}
override void playerEntered(PlayerEvent e) {
let p = players[e.playerNumber].mo;
if(p && !p.findInventory("VhtQuestHolder")) {
p.giveInventoryType("VhtQuestHolder");
}
if(!m_fGetQuests) {
m_fGetQuests = new("VhtFnPlayerInvExist").vhtInit("VhtQuestHolder");
}
let qh = vhtGetQuests();
if(!e.isReturn && m_fGetQuests.m_player == e.playerNumber) {
qh.vhtAddQuest("VhtQuest" .. level.levelNum);
}
}
override void playerDisconnected(PlayerEvent e) {
let p = players[e.playerNumber].mo;
if(p) {
let qh_r = VhtQuestHolder(p.findInventory("VhtQuestHolder"));
if(qh_r) {
p.removeInventory(qh_r);
let qh_l = vhtGetQuests();
if(qh_l) {
qh_r.m_quests.move(qh_l.m_quests);
}
}
}
}
override void worldTick() {
m_useQuestLog = vht_player_questlog;
let qh = vhtGetQuests();
if(!qh) {
return;
}
for(int i = 0, j = qh.m_quests.size(); i < j; ++i) {
if(qh.m_quests[i]) {
qh.m_quests[i].vhtTick();
}
}
if(m_useQuestLog) {
m_questLog = "";
for(int i = 0, j = qh.m_quests.size(); i < j; ++i) {
if(qh.m_quests[i]) {
m_questLog.appendFormat(
"\cu- \cn%s\c-\n%s\n\n",
StringTable.localize(
qh.m_quests[i].vhtLevelInfo().levelName,
false
),
qh.m_quests[i].vhtDescribe()
);
}
}
}
}
override void renderUnderlay(RenderEvent e) {
if(automapActive && m_useQuestLog) {
double x = Screen.getWidth() / 320.0;
double y = Screen.getHeight() / 8.0;
Screen.drawText(smallfont, Font.CR_UNTRANSLATED, x+x, y+x, m_questLog, DTA_CLEANNOMOVE_1,true, DTA_ALPHA,0.406, DTA_FILLCOLOR,0);
Screen.drawText(smallfont, Font.CR_UNTRANSLATED, x, y, m_questLog, DTA_CLEANNOMOVE_1,true);
}
}
}

56
vhtzs/fnplayer.zsc Normal file
View File

@ -0,0 +1,56 @@
class VhtFnPlayer abstract {
int m_player;
virtual bool vhtCall(PlayerInfo p) {return false;}
bool vhtRun() {
for(int i = 0; i < MAXPLAYERS; ++i) {
if(playerInGame[i] && vhtCall(players[i])) {
m_player = i;
return true;
}
}
return false;
}
}
class VhtFnPlayerInSector : VhtFnPlayer {
int m_idx;
VhtFnPlayer vhtInit(int idx) {
m_idx = idx;
return self;
}
override bool vhtCall(PlayerInfo p) {
return p.mo.curSector.index() == m_idx;
}
}
class VhtFnPlayerInv : VhtFnPlayer abstract {
class<Inventory> m_which;
Inventory m_result;
override bool vhtCall(PlayerInfo p) {
let inv = p.mo.findInventory(m_which);
if(inv) {
m_result = inv;
return true;
}
return false;
}
}
class VhtFnPlayerInvExist : VhtFnPlayerInv {
VhtFnPlayerInv vhtInit(class<Inventory> which) {
m_which = which;
return self;
}
}
class VhtFnPlayerInvAmount : VhtFnPlayerInv {
int m_amount;
VhtFnPlayerInv vhtInit(class<Inventory> which, int amount) {
m_which = which;
m_amount = amount;
return self;
}
override bool vhtCall(PlayerInfo p) {
return super.vhtCall(p) && m_result.amount >= m_amount;
}
}

12
vhtzs/fx.zsc Normal file
View File

@ -0,0 +1,12 @@
class VhtPickupFlash : PickupFlash replaces PickupFlash {
default {
+Bright;
}
}
class VhtMageWandSmoke : MageWandSmoke replaces MageWandSmoke {
default {
RenderStyle "Add";
Translation "156:163=217:223";
}
}

49
vhtzs/monsters.zsc Normal file
View File

@ -0,0 +1,49 @@
class VhtEttin : Ettin replaces Ettin {
default {
AttackSound "";
}
states {
Melee:
ETTN EF 6 a_faceTarget;
ETTN G 8 a_customMeleeAttack(random[EttinAttack](1, 8) * 2, "EttinAttack", "FighterPunchMiss");
goto See;
}
}
class VhtCentaur : Centaur replaces Centaur {
default {
AttackSound "";
}
void vhtSetReflectiveInvulnerable() {
bInvulnerable = true;
if(vht_monster_centaur != 0)
bReflective = true;
}
void vhtUnsetReflectiveInvulnerable() {
bInvulnerable = false;
if(vht_monster_centaur != 0)
bReflective = false;
}
override int damageMobj(Actor inflictor, Actor source, int damage, Name mod, int flags, double hitAngle) {
if(vht_monster_centaur == 2 && bInvulnerable) {
double angleToSrc = inflictor ? angleTo(inflictor) - angle : hitAngle;
if(angleToSrc < -90 || angleToSrc > 90) {
flags |= DMG_FOILINVUL;
}
}
return super.damageMobj(inflictor, source, damage, mod, flags, angle);
}
states {
Pain:
CENT G 6 a_pain;
CENT G 6 vhtSetReflectiveInvulnerable;
CENT EEE 15 a_centaurDefend;
CENT E 1 vhtUnsetReflectiveInvulnerable;
goto See;
Melee:
CENT H 5 a_faceTarget;
CENT I 4 a_faceTarget;
CENT J 7 a_customMeleeAttack(random[CentaurAttack](3, 9), "CentaurAttack", "FighterHammerMiss");
goto See;
}
}

38
vhtzs/player.zsc Normal file
View File

@ -0,0 +1,38 @@
mixin class VhtPlayer {
bool m_touchShatter;
override bool canCollideWith(Actor other, bool passive) {
if(m_touchShatter && other.bIceCorpse && other.distance3D(other) <= other.radius + 4) {
other.damageMobj(self, self, 1, 'Melee');
}
return true;
}
override void tick() {
m_touchShatter = vht_player_touchshatter;
super.tick();
}
}
class VhtFighterPlayer : FighterPlayer {
mixin VhtPlayer;
default {
Player.WeaponSlot 1, "FWeapFist";
Player.WeaponSlot 2, "VhtFWeapAxe";
Player.WeaponSlot 3, "FWeapHammer";
Player.WeaponSlot 4, "FWeapQuietus";
}
}
class VhtClericPlayer : ClericPlayer {
mixin VhtPlayer;
}
class VhtMagePlayer : MagePlayer {
mixin VhtPlayer;
default {
Player.StartItem "VhtMWeapWand";
Player.WeaponSlot 1, "VhtMWeapWand";
Player.WeaponSlot 2, "MWeapFrost";
Player.WeaponSlot 3, "MWeapLightning";
Player.WeaponSlot 4, "MWeapBloodscourge";
}
}

67
vhtzs/projectiles.zsc Normal file
View File

@ -0,0 +1,67 @@
class VhtMageWandMissile : MageWandMissile replaces MageWandMissile {
default {
DamageFunction vhtDamageFunc();
}
override void beginPlay() {
super.beginPlay();
spriteOffset = (0, 8);
}
int vhtDamageFunc() {
switch(vht_mwand_damagefunc) {
case 0: return 2 * random(1, 8); /* vanilla (2d8) */
case 1: return 4 * random(1, 4); /* modified (4d4) */
case 2: return 16; /* constant */
}
return 0;
}
}
class VhtFrostMissile : FrostMissile replaces FrostMissile {
default {
DamageFunction vhtDamageFunc();
}
int vhtDamageFunc() {
switch(vht_mfrost_damagefunc) {
case 0: return 1 * random(1, 8); /* vanilla (1d8) */
case 1: return 4 * random(1, 3); /* modified (4d3) */
case 2: return 8; /* constant */
}
return 0;
}
}
class VhtBloodscourgeShot : MageStaffFx2 replaces MageStaffFx2 {
override void beginPlay() {
super.beginPlay();
if(vht_mbloodscourge_foilinvul) {
bFoilInvul = true;
}
}
}
class VhtAxeMissile : Actor {
default {
Speed 25;
Radius 13;
Height 8;
Damage 2;
DamageType "Melee";
Projectile;
DeathSound "MageLightningZap";
Obituary "$OB_VHTFWEAPAXE";
Translation "146:163=146:160", "217:223=155:163";
}
states {
Spawn:
SHRD A 2 bright;
SHRD ABC 3 bright;
loop;
Death:
MWND E 4 bright;
MWND F 3 bright;
MWND G 4 bright;
MWND H 3 bright;
MWND I 4 bright;
stop;
}
}

155
vhtzs/quest.zsc Normal file
View File

@ -0,0 +1,155 @@
class VhtQuest abstract play {
int m_mapNum, m_step, m_flags;
VhtHubQuest m_hubQuest;
virtual VhtQuest vhtInit(VhtHubQuest hubQuest) {
m_mapNum = level.levelNum;
m_hubQuest = hubQuest;
return self;
}
virtual void vhtTick() {}
virtual void vhtTravelled() {}
virtual void vhtPreTravelled() {}
virtual string vhtDescribe() {
let s = StringTable.localize("$VHT_QST_" .. m_mapNum .. "_" .. m_step);
s.replace("@=", multiplayer ? "us" : "me");
s.replace("@'", multiplayer ? "our" : "my");
s.replace("@", multiplayer ? "we" : "i");
return s;
}
LevelInfo vhtLevelInfo() const {
return LevelInfo.findLevelByNum(m_mapNum);
}
}
class VhtHubQuest : VhtQuest abstract {
override VhtQuest vhtInit(VhtHubQuest hubQuest) {
super.vhtInit(self);
vhtInitHub();
return self;
}
virtual void vhtInitHub() {}
}
// quest 1: winnowing hall
class VhtQuest1 : VhtHubQuest {
VhtFnPlayer m_fStep0; // quest 1 step 0: walk into courtyard
VhtFnPlayer m_fStep1; // quest 1 step 1: pick up silver key
Line m_lStep2; // quest 1 step 2: ring the bell
override void vhtInitHub() {
m_fStep0 = new("VhtFnPlayerInSector") .vhtInit(115);
m_fStep1 = new("VhtFnPlayerInvAmount").vhtInit("KeySilver", 1);
m_lStep2 = level.lines[level.createLineIdIterator(2).next()];
}
// same hub, but cannot return
override void vhtTravelled() {
destroy();
}
override void vhtTick() {
switch(m_step) {
case 0: if(m_fStep0.vhtRun()) {m_step = 1;} break;
case 1: if(m_fStep1.vhtRun()) {m_step = 2;} // -->
case 2: if(!(m_lStep2.flags & Line.ML_BLOCKING)) {m_step = 3;} break;
}
}
}
// quest 2: seven portals
class VhtQuest2 : VhtHubQuest {
// quest 2 step 1: 3 switches pulled in first puzzle
int m_guardianOfFire, m_guardianOfSteel;
override void vhtTravelled() {
// quest 2 step 0: return from guardian of ice
if(m_step == 0 && level.levelNum == m_mapNum) {
++m_step;
}
}
override string vhtDescribe() {
let s = super.vhtDescribe();
if(m_step == 1) {
s = string.format(s, m_guardianOfFire, m_guardianOfSteel);
}
return s;
}
}
// quest 3: guardian of ice
class VhtQuest3 : VhtQuest {
override void vhtPreTravelled() {
// quest 3 step 0: exit to hub
if(m_step == 0) {
++m_step;
}
}
}
// quest 4: guardian of fire
class VhtQuest4 : VhtQuest {}
// quest 5: guardian of steel
class VhtQuest5 : VhtQuest {}
// quest 6: bright crucible
class VhtQuest6 : VhtQuest {}
class VhtQuest13 : VhtHubQuest {}
class VhtQuest8 : VhtQuest {}
class VhtQuest9 : VhtQuest {}
class VhtQuest10 : VhtQuest {}
class VhtQuest12 : VhtQuest {}
class VhtQuest11 : VhtQuest {}
class VhtQuest27 : VhtHubQuest {}
class VhtQuest32 : VhtQuest {}
class VhtQuest33 : VhtQuest {}
class VhtQuest34 : VhtQuest {}
class VhtQuest28 : VhtQuest {}
class VhtQuest30 : VhtQuest {}
class VhtQuest31 : VhtQuest {}
class VhtQuest22 : VhtHubQuest {}
class VhtQuest21 : VhtQuest {}
class VhtQuest23 : VhtQuest {}
class VhtQuest24 : VhtQuest {}
class VhtQuest25 : VhtQuest {}
class VhtQuest26 : VhtQuest {}
class VhtQuest35 : VhtHubQuest {}
class VhtQuest36 : VhtQuest {}
class VhtQuest37 : VhtQuest {}
class VhtQuest38 : VhtQuest {}
class VhtQuest39 : VhtQuest {}
class VhtQuest40 : VhtHubQuest {}
class VhtQuestHolder : Inventory {
default {
Inventory.InterHubAmount 0;
+Inventory.UnDroppable;
+Inventory.UnClearable;
}
VhtHubQuest m_hubQuest;
array<VhtQuest> m_quests;
void vhtAddQuest(class<VhtQuest> ty) {
if(ty) {
let qst = VhtQuest(new(ty)).vhtInit(m_hubQuest);
if(qst is "VhtHubQuest") {
m_hubQuest = VhtHubQuest(qst);
}
m_quests.push(qst);
}
}
override void depleteOrDestroy() {
m_hubQuest = null;
m_quests.clear();
}
override void preTravelled() {
for(int i = 0, j = m_quests.size(); i < j; ++i) {
m_quests[i].vhtPreTravelled();
}
}
override void travelled() {
for(int i = 0, j = m_quests.size(); i < j; ++i) {
m_quests[i].vhtTravelled();
}
}
}

49
vhtzs/weapons.zsc Normal file
View File

@ -0,0 +1,49 @@
class VhtFWeapAxe : FWeapAxe replaces FWeapAxe {
override void beginPlay() {
super.beginPlay();
if(vht_faxe_projectile) {
ammoUse1 = 1;
}
}
action void vhtFAxeAttack() {
if(!player) return;
if(vht_faxe_projectile) {
let wep = player.readyWeapon;
if(wep.ammo1 && wep.ammo1.amount > 0) {
a_fireProjectile("VhtAxeMissile");
}
}
a_fAxeAttack();
}
states {
FireGlow:
FAXE N 4 offset(15, 32);
FAXE O 3 offset(15, 32);
FAXE P 2 offset(15, 32);
FAXE P 1 offset(-5, 70) vhtFAxeAttack;
FAXE P 2 offset(-25, 90);
FAXE Q 1 offset(15, 32);
FAXE Q 2 offset(10, 54);
FAXE Q 7 offset(10, 150);
FAXE A 1 offset(0, 60) a_reFire;
FAXE A 1 offset(0, 52);
FAXE A 1 offset(0, 44);
FAXE A 1 offset(0, 36);
FAXE A 1;
goto ReadyGlow;
}
}
class VhtMWeapWand : MWeapWand replaces MWeapWand {
states {
Fire:
MWND A 0 a_setTics(6 - clamp(vht_mwand_speed, 0, 6));
MWND B 0 bright offset(0, 48) {
a_setTics(6 - clamp(vht_mwand_speed, 0, 6));
a_fireProjectile("MageWandMissile");
}
MWND A 3 offset(0, 40);
MWND A 3 offset(0, 36) a_reFire;
goto Ready;
}
}

10
zscript.zsc Normal file
View File

@ -0,0 +1,10 @@
version "4.8"
#include "vhtzs/fnplayer.zsc"
#include "vhtzs/fx.zsc"
#include "vhtzs/projectiles.zsc"
#include "vhtzs/weapons.zsc"
#include "vhtzs/player.zsc"
#include "vhtzs/monsters.zsc"
#include "vhtzs/quest.zsc"
#include "vhtzs/events.zsc"