note
	description: "[
		p_pspr.c
		Weapon sprite animation, weapon objects.
		Action functions for weapons.
	]"
	license: "[
		Copyright (C) 1993-1996 by id Software, Inc.
		Copyright (C) 2005-2014 Simon Howard
		Copyright (C) 2021 Ilgiz Mustafin
		
		This program is free software; you can redistribute it and/or modify
		it under the terms of the GNU General Public License as published by
		the Free Software Foundation; either version 2 of the License, or
		(at your option) any later version.
		
		This program is distributed in the hope that it will be useful,
		but WITHOUT ANY WARRANTY; without even the implied warranty of
		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
		GNU General Public License for more details.
		
		You should have received a copy of the GNU General Public License along
		with this program; if not, write to the Free Software Foundation, Inc.,
		51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
	]"

class 
	P_PSPR

inherit
	WEAPONTYPE_T

	AMMOTYPE_T

	SFXENUM_T

	STATENUM_T

create 
	make

feature 

	i_main: I_MAIN

	make (a_i_main: like i_main)
		do
			i_main := a_i_main
		end
	
feature -- psprnum_t

	Ps_weapon: INTEGER_32 = 0

	Ps_flash: INTEGER_32 = 1

	Numpsprites: INTEGER_32 = 2

	Ff_framemask: INTEGER_32 = 32767

	Ff_fullbright: INTEGER_32 = 32768
			-- flag in thing->frame

	Bfgcells: INTEGER_32 = 40
			-- plasma cells for a bfg attack
	
feature 

	Weaponbottom: INTEGER_32
		once
			Result := 128 * {M_FIXED}.fracunit
		ensure
			instance_free: class
		end

	Weapontop: INTEGER_32
		once
			Result := 32 * {M_FIXED}.fracunit
		end

	Raisespeed: INTEGER_32
		once
			Result := {M_FIXED}.fracunit * 6
		ensure
			instance_free: class
		end

	Lowerspeed: INTEGER_32
		once
			Result := {M_FIXED}.fracunit * 6
		end
	
feature 

	a_raise (player: PLAYER_T; psp: PSPDEF_T)
		local
			newstate: INTEGER_32
		do
			psp.sy := psp.sy - create {FIXED_T}.from_integer (Raisespeed)
			if psp.sy > create {FIXED_T}.from_integer (Weapontop) then
			else
				psp.sy := create {FIXED_T}.from_integer (Weapontop)
				newstate := {D_ITEMS}.weaponinfo [player.readyweapon].readystate
				p_setpsprite (player, Ps_weapon, newstate)
			end
		end

	a_weaponready (player: PLAYER_T; psp: PSPDEF_T)
			-- The player can fire the weapon
			-- or change to another weapon at this time.
			-- Follows after getting weapon up,
			-- or after previous attack/fire sequence.
		local
			newstate: INTEGER_32
			angle: INTEGER_32
			returned: BOOLEAN
		do
			check
					attached player.mo as mo
			then
				if mo.state = {INFO}.states [{INFO}.s_play_atk1] or mo.state = {INFO}.states [{INFO}.s_play_atk2] then
					i_main.P_mobj.p_setmobjstate (mo, {INFO}.s_play).do_nothing
				end
				if player.readyweapon = Wp_chainsaw and psp.state = {INFO}.states [{INFO}.s_saw] then
					i_main.S_sound.s_startsound (mo, {SFXENUM_T}.sfx_sawidl)
				end
				if player.pendingweapon /= Wp_nochange or player.health = 0 then
					newstate := {D_ITEMS}.weaponinfo [player.readyweapon].downstate
					p_setpsprite (player, Ps_weapon, newstate)
					returned := True
				end
				if not returned then
					if player.cmd.buttons & {D_EVENT}.bt_attack /= 0 then
						if not player.attackdown or (player.readyweapon /= Wp_missile and player.readyweapon /= Wp_bfg) then
							player.attackdown := True
							p_fireweapon (player)
							returned := True
						end
					else
						player.attackdown := False
					end
				end
				if not returned then
					angle := (128 * i_main.P_tick.leveltime) & {R_MAIN}.finemask
					psp.sx := create {FIXED_T}.from_integer ({M_FIXED}.fracunit + {M_FIXED}.fixedmul (player.bob, {R_MAIN}.finecosine [angle]).as_integer_32)
					angle := angle & ({R_MAIN}.fineangles // 2 - 1)
					psp.sy := create {FIXED_T}.from_integer (Weapontop + {M_FIXED}.fixedmul (player.bob, create {FIXED_T}.from_integer ({R_MAIN}.finesine [angle])).as_integer_32)
				end
			end
		end
	
feature 

	p_fireweapon (player: PLAYER_T)
		local
			newstate: INTEGER_32
		do
			if not p_checkammo (player) then
			else
				check
						attached player.mo as mo
				then
					i_main.P_mobj.p_setmobjstate (mo, {STATENUM_T}.s_play_atk1).do_nothing
					newstate := {D_ITEMS}.weaponinfo [player.readyweapon].atkstate
					p_setpsprite (player, Ps_weapon, newstate)
					i_main.P_enemy.p_noisealert (mo, mo)
				end
			end
		end

	p_checkammo (player: PLAYER_T): BOOLEAN
			-- Returns true if there is enough ammo to shoot.
			-- If not, selects the next weapon to use.
		local
			ammo: INTEGER_32
			count: INTEGER_32
		do
			ammo := {D_ITEMS}.weaponinfo [player.readyweapon].ammo
			if player.readyweapon = Wp_bfg then
				count := Bfgcells
			elseif player.readyweapon = Wp_supershotgun then
				count := 2
			else
				count := 1
			end
			if ammo = Am_noammo or player.ammo [ammo] >= count then
				Result := True
			else
				from
					player.pendingweapon := Wp_nochange
				until
					player.pendingweapon /= Wp_nochange
				loop
					if player.weaponowned [Wp_plasma] and player.ammo [Am_cell] /= 0 and i_main.Doomstat_h.gamemode /= {GAME_MODE_T}.shareware then
						player.pendingweapon := Wp_plasma
					elseif player.weaponowned [Wp_supershotgun] and player.ammo [Am_shell] > 2 and i_main.Doomstat_h.gamemode = {GAME_MODE_T}.commercial then
						player.pendingweapon := Wp_supershotgun
					elseif player.weaponowned [Wp_chaingun] and player.ammo [Am_clip] /= 0 then
						player.pendingweapon := Wp_chaingun
					elseif player.weaponowned [Wp_shotgun] and player.ammo [Am_shell] /= 0 then
						player.pendingweapon := Wp_shotgun
					elseif player.ammo [Am_clip] /= 0 then
						player.pendingweapon := Wp_pistol
					elseif player.weaponowned [Wp_chainsaw] then
						player.pendingweapon := Wp_chainsaw
					elseif player.weaponowned [Wp_missile] and player.ammo [Am_misl] /= 0 then
						player.pendingweapon := Wp_missile
					elseif player.weaponowned [Wp_bfg] and player.ammo [Am_cell] > 40 and (i_main.Doomstat_h.gamemode /= {GAME_MODE_T}.shareware) then
						player.pendingweapon := Wp_bfg
					else
						player.pendingweapon := Wp_fist
					end
				end
				p_setpsprite (player, Ps_weapon, {D_ITEMS}.weaponinfo [player.readyweapon].downstate)
				Result := False
			end
		end
	
feature 

	p_setuppsprites (player: PLAYER_T)
			-- Called at start of level for each player
		local
			i: INTEGER_32
		do
			from
				i := 0
			until
				i >= Numpsprites
			loop
				player.psprites [i].state := Void
				i := i + 1
			end
			player.pendingweapon := player.readyweapon
			p_bringupweapon (player)
		end

	p_bringupweapon (player: PLAYER_T)
			-- Starts bringing up the pending weapon up
			-- from the bottom of the screen.
			-- Uses player
		local
			newstate: INTEGER_32
		do
			if player.pendingweapon = Wp_nochange then
				player.pendingweapon := player.readyweapon
			end
			if player.pendingweapon = Wp_chainsaw then
				i_main.S_sound.s_startsound (player.mo, {SFXENUM_T}.sfx_sawup)
			end
			newstate := {D_ITEMS}.weaponinfo [player.pendingweapon].upstate
			player.pendingweapon := Wp_nochange
			player.psprites [Ps_weapon].sy := create {FIXED_T}.from_integer (Weaponbottom)
			p_setpsprite (player, Ps_weapon, newstate)
		end

	p_setpsprite (player: PLAYER_T; position: INTEGER_32; a_stnum: INTEGER_32)
		local
			psp: PSPDEF_T
			state: STATE_T
			break: BOOLEAN
			stnum: INTEGER_32
			did: BOOLEAN
			action_target: STRING_8
		do
			stnum := a_stnum
			psp := player.psprites [position]
			from
			until
				did and (break or psp.tics /= 0)
			loop
				did := True
				if stnum = 0 then
					psp.state := Void
					break := True
				end
				if not break then
					state := {INFO}.states [stnum]
					psp.state := state
					psp.tics := state.tics.to_integer_32
					if state.misc1 /= 0 then
						psp.sx := create {FIXED_T}.from_integer ((state.misc1 |<< {M_FIXED}.fracbits).to_integer_32)
						psp.sy := create {FIXED_T}.from_integer ((state.misc2 |<< {M_FIXED}.fracbits).to_integer_32)
					end
					if attached state.action as action then
						action.call (i_main.P_pspr, player, psp)
						if psp.state = Void then
							break := True
						end
					end
				end
				if not break then
					check
							attached psp.state as s
					then
						stnum := s.nextstate
					end
				end
			end
		end

	p_movepsprites (player: PLAYER_T)
			-- Called every tic by player thinking routine.
		local
			i: INTEGER_32
			psp: INTEGER_32
			state: STATE_T
		do
			psp := 0
			from
				i := 0
			until
				i >= Numpsprites
			loop
				state := player.psprites [psp].state
				if state /= Void then
					if player.psprites [psp].tics /= -1 then
						player.psprites [psp].tics := player.psprites [psp].tics - 1
						if player.psprites [psp].tics = 0 then
							p_setpsprite (player, i, state.nextstate)
						end
					end
				end
				psp := psp + 1
				i := i + 1
			end
			player.psprites [Ps_flash].sx := player.psprites [Ps_weapon].sx
			player.psprites [Ps_flash].sy := player.psprites [Ps_weapon].sy
		end
	
feature 

	a_light0 (player: PLAYER_T; psp: PSPDEF_T)
		do
			player.extralight := 0
		ensure
			instance_free: class
		end

	a_lower (player: PLAYER_T; psp: PSPDEF_T)
			-- Lowers current weapon,
			-- and changes weapon at bottom.
		do
			psp.sy := psp.sy + create {FIXED_T}.from_integer (Lowerspeed)
			if psp.sy < create {FIXED_T}.from_integer (Weaponbottom) then
			else
				if player.playerstate = {D_PLAYER}.pst_dead then
					psp.sy := create {FIXED_T}.from_integer (Weaponbottom)
				else
					if player.health = 0 then
						p_setpsprite (player, Ps_weapon, S_null)
					else
						player.readyweapon := player.pendingweapon
						p_bringupweapon (player)
					end
				end
			end
		end

	a_punch (player: PLAYER_T; psp: PSPDEF_T)
		local
			angle: ANGLE_T
			damage: INTEGER_32
			slope: INTEGER_32
		do
			damage := (i_main.M_random.p_random \\ 10 + 1) |<< 1
			if player.powers [{POWERTYPE_T}.pw_strength] /= 0 then
				damage := damage * 10
			end
			check
					attached player.mo as mo
			then
				angle := mo.angle
				angle := angle + create {ANGLE_T}.from_natural (((i_main.M_random.p_random - i_main.M_random.p_random) |<< 18).to_natural_32)
				slope := i_main.P_map.p_aimlineattack (mo, angle, create {FIXED_T}.from_integer ({P_LOCAL}.meleerange)).as_integer_32
				i_main.P_map.p_lineattack (mo, angle, create {FIXED_T}.from_integer ({P_LOCAL}.meleerange), create {FIXED_T}.from_integer (slope), damage)
				if attached i_main.P_map.linetarget as lt then
					i_main.S_sound.s_startsound (mo, Sfx_punch)
					mo.angle := i_main.R_main.r_pointtoangle2 (mo.x, mo.y, lt.x, lt.y)
				end
			end
		end

	a_refire (player: PLAYER_T; psp: PSPDEF_T)
			-- The player can re-fire the weapon
			-- without lowering it entirely.
		do
			if player.cmd.buttons & {D_EVENT}.bt_attack /= 0 and player.pendingweapon = Wp_nochange and player.health /= 0 then
				player.refire := player.refire + 1
				p_fireweapon (player)
			else
				player.refire := 0
				p_checkammo (player).do_nothing
			end
		end

	a_firepistol (player: PLAYER_T; psp: PSPDEF_T)
		do
			check
					attached player.mo as mo
			then
				i_main.S_sound.s_startsound (mo, Sfx_pistol)
				i_main.P_mobj.p_setmobjstate (mo, S_play_atk2).do_nothing
				player.ammo [{D_ITEMS}.weaponinfo [player.readyweapon].ammo] := player.ammo [{D_ITEMS}.weaponinfo [player.readyweapon].ammo] - 1
				p_setpsprite (player, Ps_flash, {D_ITEMS}.weaponinfo [player.readyweapon].flashstate)
				p_bulletslope (mo)
				p_gunshot (mo, not player.refire.to_boolean)
			end
		end

	bulletslope: FIXED_T

	p_bulletslope (mo: MOBJ_T)
			-- Sets a slope so a near miss is at aproximately
			-- the height of the intended target
		local
			an: ANGLE_T
		do
			an := mo.angle
			bulletslope := i_main.P_map.p_aimlineattack (mo, an, create {FIXED_T}.from_integer (16 * 64 * {M_FIXED}.fracunit))
			if i_main.P_map.linetarget = Void then
				an := an + create {ANGLE_T}.from_natural ((1 |<< 26).to_natural_32)
				bulletslope := i_main.P_map.p_aimlineattack (mo, an, create {FIXED_T}.from_integer (16 * 64 * {M_FIXED}.fracunit))
				if i_main.P_map.linetarget = Void then
					an := an - create {ANGLE_T}.from_natural ((2 |<< 26).to_natural_32)
					bulletslope := i_main.P_map.p_aimlineattack (mo, an, create {FIXED_T}.from_integer (16 * 64 * {M_FIXED}.fracunit))
				end
			end
		end

	p_gunshot (mo: MOBJ_T; accurate: BOOLEAN)
		local
			angle: ANGLE_T
			damage: INTEGER_32
		do
			damage := 5 * (i_main.M_random.p_random \\ 3 + 1)
			angle := mo.angle
			if not accurate then
				angle := angle + create {ANGLE_T}.from_natural (((i_main.M_random.p_random - i_main.M_random.p_random) |<< 18).to_natural_32)
			end
			i_main.P_map.p_lineattack (mo, angle, create {FIXED_T}.from_integer ({P_LOCAL}.missilerange), bulletslope, damage)
		end

	a_light1 (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_Light1", False)
		end

	a_light2 (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_Light2", False)
		end

	a_fireshotgun (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_FireShotgun", False)
		end

	a_fireshotgun2 (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_FireShotgun2", False)
		end

	a_checkreload (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_CheckReload", False)
		end

	a_firecgun (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_FireCGun", False)
		end

	a_gunflash (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_GunFlash", False)
		end

	a_firemissile (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_FireMissile", False)
		end

	a_saw (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_Saw", False)
		end

	a_fireplasma (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_FirePlasma", False)
		end

	a_bfgsound (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_BFGSound", False)
		end

	a_firebfg (player: PLAYER_T; psp: PSPDEF_T)
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_FireBFG", False)
		end

	a_bfgspray (mo: MOBJ_T)
			-- Spawn a BFG explosion on every monster in view
		do
			{NOT_IMPLEMENTED}.not_implemented ("A_BFGSpray", False)
		end
	
feature 

	p_dropweapon (player: PLAYER_T)
			-- Player died, so put the weapon away
		do
			p_setpsprite (player, Ps_weapon, {D_ITEMS}.weaponinfo [player.readyweapon].downstate)
		end
	
end -- class P_PSPR

Generated by ISE EiffelStudio