Search In
• More options...
Find results that contain...
Find results in...

# More PSX Doom Code

## Recommended Posts

A_PosAttack damage calculation:

```jal     P_Random                 ; v0 = P_Random()
lui     \$a2, 0x800
li      \$a3, 0x7FFFFFFF
andi    \$v0, 7                   ; v0 &= 7
addiu   \$v0, 1                   ; v0 += 1
sll     \$v1, \$v0, 1              ; v1 = v0 << 1 (same as *2)
addu    \$v1, \$v0                 ; v1 += v0     (x*2 + x == x*3)
jal     P_LineAttack
sw      \$v1, 0x28+var_18(\$sp)    ; push v1 to stack as argument
```
So, code for PlayStation zombieman's attack, in C, would be
((P_Random() & 7) + 1) * 3

This means zombiemen do damage in amounts of 3, 6, 9, 12, 15, 18, 21, and 24.

This means with the vanilla gibbing behavior, as was previously proved intact from vanilla, PSX zombiemen can indeed just barely gib each other. A zombieman with 3 health or less receiving a shot for 24 damage will gib.

So the player can't gib a zombieguy, but his buddies can?

That's messed up. =/

Giomancer said:

So the player can't gib a zombieguy, but his buddies can?

That's messed up. =/

Not with pistol shots. I haven't been able to figure out the fist's damage code yet though. It's weirdly complicated compared to what I'd expect.

Quasar said:

zombiemen do damage in amounts of 3, 6, 9, 12, 15, 18, 21, and 24

Holy shit. I always thought that damage in PSX Doom felt somewhat 'random' when compared to PC, but didn't expect it to have such a range.

BaronOfStuff said:

Holy shit. I always thought that damage in PSX Doom felt somewhat 'random' when compared to PC, but didn't expect it to have such a range.

Well, those damage values aren't particularly abnormal. I think most damage values in vanilla were calculated by setting a primary damage value and then randomly multiplying it anywhere from 1x to 8x per hit. That seems to be what's happening here as well.

Wow, nice find! No wonder I've never seen this happen.

Megamur said:

Well, those damage values aren't particularly abnormal. I think most damage values in vanilla were calculated by setting a primary damage value and then randomly multiplying it anywhere from 1x to 8x per hit. That seems to be what's happening here as well.

Yeah. What we've found so far (I and others) is that these calculations were changed in Jag Doom, and the reason they were changed was to avoid mul and mod instructions.

In the original PC, the calculation is ((P_Random()%5)+1)*3. Modulus, if it was even available as an instruction on the Jag's hardware in the first place, would be one of the most expensive instructions. So to buy CPU cycles, it makes sense, where possible, to replace it instead with an "and" with the nearest multiple of 2 minus one, because such and ops, which are VERY fast on just about all hardware, behave just like a modulus.

In this case though it has the side effect of creating a much wider range of possible values, AND a fairly significant increase in max possible damage - from 15, to 24. That means you're in the "danger zone" as soon as you drop below 25 health in PSX Doom, which is pretty brutal when you also consider the sheer volume of hitscanners they added in UV >_>

So p_random() returns a number from 0-255, say it returns 254, I assume it returns 254 as a byte like this:
11111110
Then you & it with 7.
00000111
That means if both 1s are 1, its a 1 otherwise its a 0 so:

11111110
00000111
=
00000110

Oh, I get it, the purpose of the 7 is to pack 0s at the left to change a bigger random number of 0-255 into a random number of a smaller range, 0-7 (+1 though, so 1-8). I'm just thinking as I type. Python, which I use, hides all this byte stuff seemingly which kind of sucks. I almost got into c, but installing SDL took so many steps I was put off.

Do you know if modulus is 'expensive' in python too? I use it quite a lot. I've never even used & or any bit type stuff in any python program, which feels like I'm missing out on the heart of programming.

Funny reading the doom wiki how simple p_random() is, just a shuffled list (basically, some digits missing, some repeated) of 0-255 and it increments through linearly.

Couldn't the playstation code just add a "-3":
(((P_Random() & 7) - 3) + 1) * 3
to make it (random between 1 and 5) times 3
as opposed to (random between 1 and 8) times 3
which would make 15 the highest possible instead of 24?

There's a good chance I'm talking complete gibberish (it is my primary language). What programming language is that code posted in the original post?

gggmork said:

Do you know if modulus is 'expensive' in python too? I use it quite a lot. I've never even used & or any bit type stuff in any python program, which feels like I'm missing out on the heart of programming.

On x86/x64 at least, modulus is implemented through the idiv operator for integer math. It returns the division result and remainder in the eax and edx registers simultaneously. When getting the modulus, the division result is simply discarded.

It is not the slowest opcode on x86 platform by far, but it's hardly the fastest either. Amongst integer ALU operations, division is the most complicated. A basic circuit to implement division is about twice the size of a multiplication circuit.

```Couldn't the playstation code just add a "-3":
(((P_Random() & 7) - 3) + 1) * 3
to make it (random between 1 and 5) times 3
as opposed to (random between 1 and 8) times 3
which would make 15 the highest possible instead of 24?
```
(0 - 3) + 1 is -2, and believe me, you don't want to do negative damage in the Doom engine - major undefined behavior results. You'd have to add a conditional to discard negative damage, and that would cost as much or more than you saved getting rid of the modulus, especially if the processor is pipelined and you cause a branch prediction miss. No clue if that applied to the Jag as I don't even know what excuse of a chip it used for a CPU.

The original "language" is MIPS assembly for the R3000A, though, it is "simplified" by IDA Pro for output by omitting repeating operands to opcodes (which is confusing as hell if you're used to "verbose" standard MIPS asm). That's the CPU used by the original PlayStation console. A similar but more powerful CPU was used in the N64.

r = P_Random()
((r&3)+(r&&4) + 1) * 3
(first bitwise and, second logical and)

say p_random() returns 10010110

& by 3 to produce a random choice between (00,01,10,11)
10010110
00000011
=
00000010

(00,01,10,11) are only 4 possible choices but you want 5, so + it to logical && of 4, so you get that other digit:
10010110
00000100
= True, ie 1, not 0. So sometimes it adds 1 to the other 4 choices, making for 5 possible choices total.

Not sure if I know what I'm talking about.

I always felt the monsters did more damage in Playstation Doom and I did notice hitscanners in particular seemed quite lethal.

I have always argued that the Revenant was crippled in this port, but having played though three Lost Levels that actually use them cleverly (an implied criticism of original mappers, not the converters), it seems that they are capable of considerable harm. Not only do their missiles always follow you, as someone pointed out in the Lost Levels thread, they move slowly and are capable of much tighter turning circles, so you cannot simply sidestep in a small room, you really need to find cover. I'd be interested to know what the damage code is for Revenants. They seem to do 40+ damage with their missiles.

Also, do monsters do more damage hand-to-hand? It often seems that way.

On PC, Revenant missiles are apparently capable of up to 80 damage a hit.

http://doomwiki.org/wiki/Revenant#Data

I have been locked in place on maps with the game ensuring each Revenant rocket did the full 80 >_>

Yet another reason to hate the bastards, the RNG deciding that you must kill a large group of them in a small space without a scratch or suffer 80 per hit every time. Sometimes the RNG seems sentient..

Ragnor said:

I have been locked in place on maps with the game ensuring each Revenant rocket did the full 80 >_>

Yet another reason to hate the bastards, the RNG deciding that you must kill a large group of them in a small space without a scratch or suffer 80 per hit every time. Sometimes the RNG seems sentient..

Doom is highly subject to Murphy's law. For example when you actually have less than 15 health, then every bullet will do 15 and not anything less. If you are trying to Tyson a Baron and get scratched, it'll be for the full possible 80 damage. And of course if you have nowhere to hide, every Revenant missile is homing :P

More damage values verified. The following are all the same as they were in Jag Doom (not same as vanilla):

• Imp scratch: ((rnd&7)+1)*3
• Demon bite: ((rnd&7)+1)*4
• Caco bite: ((rnd&7)+1)*8
• Baron scratch: ((rnd&7)+1)*11
Imp, caco, and baron attacks are all direct damage; the Demon bite is a hitscan fired at MELEERANGE distance, as was already previously known.

The Revenant's punch attack on the other hand is still ((rnd%10)+1)*6, like it was in vanilla Doom 2. Appears that the PSX team didn't apply Jag-style optimizations to imported Doom 2 code. As a result, the code is really inefficient - the % 10 part of the calculation requires a full 10 instructions to execute, partially thanks to the compiler being unable to make certain assumptions that would be obvious to a human programmer (such as the output of P_Random always being positive).

Very interesting, Quasar! Good job. About the demon bite; Is it the kind of hitscan attack that could provoke infighting, or is it the "normal" one (which doesn't, usually)?

Olroda said:

About the demon bite; Is it the kind of hitscan attack that could provoke infighting, or is it the "normal" one (which doesn't, usually)?

What? All hitscan attacks can provoke infighting. Demons simply had their attack changed to apply direct damage in later engine versions.

Oh, right. That's what I meant, but my memory of it was a bit fuzzy. Thanks for clearing it up, Baron of Stuff. I'm supposing it would be the latter, direct method that would be in the console ports...

`((P_Random() & 7) + 1) * 3`
Isn't the max value is 21? X mod 7 can't be higher than 6.

It's not 'random % 7', it's 'random & 7' (bitwise AND operation). Since 7 is 111b, it is effectively equivalent to 'random % 8', but faster since it uses ultra-fast bitwise ANDing instead of sluggish division. (You can only use the x&y-1 == x%y for values of Y that are a power of 2.)

So it can get as high as 7. Then +1 makes it 8, and * 3 makes it 24.

Gez said:

It's not 'random % 7', it's 'random & 7' (bitwise AND operation). Since 7 is 111b, it is effectively equivalent to 'random % 8'. So it can get as high as 7. Then +1 makes it 8, and * 3 makes it 24.

Oh, my mistake. Thanks for correction.

Olroda said:

I'm supposing it would be the latter, direct method that would be in the console ports...

Nope. To be honest, I don't know why this was changed in the PC version. The direct-damage method turned Demons/Spectres into mobile obstacles as opposed to legitimate (if still easy) threats.

BaronOfStuff said:

Nope. To be honest, I don't know why this was changed in the PC version. The direct-damage method turned Demons/Spectres into mobile obstacles as opposed to legitimate (if still easy) threats.

I honestly think it might have been because the hitscan would cause a bullet puff if it hit a wall, which is bizarre. They didn't want to bother hacking that out when it was easier to make it direct damage like the rest of enemies' melee attacks.

That's interesting. So, did only demons (assuming spectres as well) have the non-direct damage variant initially?

Yes.

And yeah, spectres use the same function (A_SargAttack).

Gez said:

Yes.

And yeah, spectres use the same function (A_SargAttack).

All spectres, it's worth saying. The normal translucent ones and Nightmare Spectres all use the Demon's state and mobjinfo, and just get special flags applied to them in the map to make them look different (the NM Spectre's spawnhealth is also doubled; I need to find the code that actually does that for posterity).

Quasar said:

All spectres ... use the Demon's state and mobjinfo, and just get special flags applied to them in the map to make them look different.

So there's not actually a separate Spectre actor? Well that's one way to save space. Speaking of Spectres, is there any particular reason why the translucency varies between them? I don't have any videos or screenshots on hand, but I remember that some of them would be almost opaque while others were practically invisible.

Maybe it's simply down to sector colour and light levels, but I'd like to be sure if possible.

BaronOfStuff said:

So there's not actually a separate Spectre actor? Well that's one way to save space. Speaking of Spectres, is there any particular reason why the translucency varies between them? I don't have any videos or screenshots on hand, but I remember that some of them would be almost opaque while others were practically invisible.

Maybe it's simply down to sector colour and light levels, but I'd like to be sure if possible.

Kaiser and I have not figured out how it works... there are three separate bit flags used on Spectres. For most Spectres, one of two sets of flag values is used. I am going by memory here but, if all three bits are turned on, the Spectre appears to be additive with approximately 25% foreground strength. On the other hand if only two of the bits (which two I can't remember right now) are turned on, it becomes a subtractive Nightmare Spectre with 300 health.

All of these flags work on other monsters as well. There is a cacodemon in Tenements which is apparently accidentally transparent (it has the same flags as an additive spectre IIRC).

Some people are saying that spectres occur in more than one translucency value. If you can identify decisively where any of these spectres spawn (MAP # and, if possible, closest coordinate values to where it spawns or is found), it would really help us out.

Where's that spectre Cacodemon at? Never took notice to it myself.

This might help:

Omnipotus said:

There is a flag in PSXDoom which causes a sprite to become inverted, transparent, and multiply the HP by 2.

I discovered this several months ago after hacking the PSXDoom rom I ripped myself and took a look at the MAP05 things list and searched around for the flag for the Nightmare Spectre thing at the very beginning of the level.

The AC hex number (or decimal, 172) was the flag for that THING.
So I changed the flag to something like EC and the Nightmare Spectre became a regular Spectre. I was able to try the flag on other sprites which resulted in Imps, Shotgun Guys, and Medikits to become inverted and transparent and a shy bit tougher (exclude the Medikit). You could actually just open up a sprite in an image program and invert the sprite and perhaps lower the brightness and raise the contrast.
I believe the actual Nightmare thing flag is 160.

Some others tidbits:
The common transparent thing flag is appears to be 224.
The transparent thing flag 32, is like 224, but appears brighter, MAP25 and MAP34 are a couple of levels that use this on Spectres.
The transparent flag for walls is 1024.

Oh, one other thing I found out was that PSXDoom still had the Nightmare Mode intact, though, how to play that skill I am unsure of, but how I found it was I modified DEMO1.LMP's skill level and changed it to one number higher, which resulted the game demo playing on high caffeine so to speak.

Dragonsbrethren said:

Where's that spectre Cacodemon at? Never took notice to it myself.

This might help:

I already know what the three flag values are (none of them match that descrition though; numbers like 160 and 224 are not "flags"; they would be combinations of individual flags). A pure flag value has to be a power of two; ex: 128, 256, 512, 1024, 2048, 4096. What I don't know is what happens when the "other" combination of just two of the flags is used - the two I don't usually see together in isolation. I'll check those maps mentioned there however and see if they contain the allegedly variant spectre variety.

The ghost caco is in the cage where an Arch-vile used to be in Tenements. It's in an area where you go around the narrow perimeter; the cage is inside a pillar in the middle of a brown slime pool. Hope that's enough for you to understand where I mean. It's in the latter part of the level; before you go back across and ride up a lift that takes you to the area where you have to run back and forth between three different paths with blaze open door actions at the end of each.

Also I'd like to reiterate that the Nightmare Spectre is *not* inverted in any way. To invert color in RGB space would be done with the operation (255 - c). This operation results in black becoming white, white becoming black, etc. Like when you get an invuln sphere in Doomsday. If you try to use that operation to get the NM Spectre effect you will see it's completely wrong.

The operation used to blend NM spectres is simply called "subtractive blending". And it appears from screenshots that the sprite is subtracted at or very very near to 100% strength. The equation is (background - sprite pixel), and then capping the result to 0 (color values can't be negative).