Skip to content

PR to add Treyarc Function of ViewmodelFX and further improve!#360

Open
neoXclusive wants to merge 11 commits intoiw4x:mainfrom
neoXclusive:viewmodel-fx-support
Open

PR to add Treyarc Function of ViewmodelFX and further improve!#360
neoXclusive wants to merge 11 commits intoiw4x:mainfrom
neoXclusive:viewmodel-fx-support

Conversation

@neoXclusive
Copy link

@neoXclusive neoXclusive commented Jan 30, 2026

ALL CREDITS TO ANTIGA @EINFLICTOR

Viewmodel Fx at its current state only works on local host.

Pr is to get this code out into the world! Like i said for now Fx is only playing on local client, for all clients to see their own viewmodelfx in an online server this code still needs work. This is what im hoping to achieve in the end goal of releasing the code!

Function naming could probably be better on a release version if it ever gets improved.

heres some base gsc so you can see how the functionality works within gsc.

If you are interested in reviewing code and need a fast mod to test on, ill send the tesla gun mod your way. Just dm on cord @myluckyneo

Init()
{
	level.tesla_gun = "t4_dg2_mp";
	replaceFunc( maps\mp\gametypes\_gamelogic::matchStartTimerPC, maps\mp\gametypes\_gamelogic::matchStartTimerSkip );
	level thread onPlayerConnected();
	
	level._teslaFX = [];

	level._teslaFX["tesla_viewmodel_view"]  = "sk/t5/weapons/tesla/fx_zombie_tesla_tube_view";
	level._teslaFX["tesla_viewmodel_view2"] = "sk/t5/weapons/tesla/fx_zombie_tesla_tube_view2";
	level._teslaFX["tesla_viewmodel_view3"] = "sk/t5/weapons/tesla/fx_zombie_tesla_tube_view3";
	level._teslaFX["tesla_viewmodel_rail"] = "sk/t5/weapons/tesla/fx_zombie_tesla_rail_view";
}

onPlayerConnected()
{
	for( ;; )
	{
		level waittill( "connected", player );
		player thread bulbFX();
		player thread afterShotFX();
		player thread railIdleFX();
	    player thread onPlayerSpawned();
    }
}

// Called on player spawn (server)
onPlayerSpawned()
{
    self endon("disconnect");

    self waittill("spawned_player");

    // Give weapon (server-side)
    self takeAllWeapons();
    self giveWeapon(level.tesla_gun);
    wait 1;
    self switchToWeapon(level.tesla_gun);
}

railIdleFX()
{
	level endon("game_ended");
	for (;;)
	{
		wait 10;
		PVMFX_BOTH(level._teslaFX["tesla_viewmodel_rail"], "tag_flash");
	}
}

bulbFX()
{
    level endon("game_ended");

    for (;;)
    {
		
        wait 0.1;
		if( self getCurrentWeapon() != level.tesla_gun )
			continue;

        ammo = self GetWeaponAmmoClip( self getCurrentWeapon() );

        switch(ammo)
        {
            case 3:
                PVMFX_BOTH(level._teslaFX["tesla_viewmodel_view"], "tag_brass");
                break;
            case 2:
                PVMFX_BOTH(level._teslaFX["tesla_viewmodel_view2"], "tag_brass");
                break;
            case 1:
                PVMFX_BOTH(level._teslaFX["tesla_viewmodel_view3"], "tag_brass");
                break;
		
        }
    }
}
afterShotFX()
{
    level endon("game_ended");
    self endon("disconnect");

    for (;;)
    {
        self waittill("weapon_fired", weapon);

        if (weapon == level.tesla_gun)
        {
            wait 0.1;
            PVMFX_BOTH(level._teslaFX["tesla_viewmodel_rail"], "tag_flash");
            IPrintLnBold(">> [TESLA DEBUG] Local viewmodel FX played for ", self.name);
        }
    }
}

@neoXclusive neoXclusive changed the title PR to add Treyarc Function of PVMFX_BOTH further improve Viewmodel FX in iw4x! PR to add Treyarc Function of Viewmodel further improve ViewmodelFX in iw4x! Jan 30, 2026
@neoXclusive neoXclusive changed the title PR to add Treyarc Function of Viewmodel further improve ViewmodelFX in iw4x! PR to add Treyarc Function of ViewmodelFX further improve ViewmodelFX in iw4x! Jan 30, 2026
@neoXclusive neoXclusive changed the title PR to add Treyarc Function of ViewmodelFX further improve ViewmodelFX in iw4x! PR to add Treyarc Function of ViewmodelFX and further improve! Jan 30, 2026
Co-authored-by: William Roy <wroy@proton.me>
Comment on lines +78 to +80
Game::CG_PlayBoltedEffect(0, fx, fpDObjHandle, tagName);

Game::CG_StopBoltedEffect(0, 0, fpDObjHandle, tagName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: What is the intent here? The effect is played and then immediately stopped in the next instruction, which would likely result in no visible effect. Is this intentional, or is there some non-obvious detail about how GSC interacts with the engine that I'm missing?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iirc CG_PlayBoltedEffect starts the fx in the viewport but without CG_StopBoltedEffect i know it continues to play fx forever (rather than say an fx is only released on reload and stops when its loaded)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is GSC suppose to be calling stop vfx at a certain point, and not the engine? CG_StopBoltedEffect seems like what you would call after the fx needs to be done with, but this function could be used for general purpose and not only wunderwoff

Co-authored-by: William Roy <wroy@proton.me>
typedef const DObj*(*CG_GetBoneIndex_t)(int localClientNum, unsigned int boneName, char* boneIndex);
extern CG_GetBoneIndex_t CG_GetBoneIndex;

typedef int(__cdecl* DObjGetBoneIndex_t)(int a1, int a2, unsigned char* a3);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: I believe a1 is probably const DObj* dobj or DObj* dobj ?


namespace Components::ViewModelFxSetup
{
static char cg_weaponsArray[32 * Weapon::WEAPON_LIMIT];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never used?

@@ -0,0 +1,10 @@
#pragma once

namespace Components::ViewModelFxSetup
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
namespace Components::ViewModelFxSetup
namespace Components

@wroyca
Copy link
Member

wroyca commented Jan 30, 2026

Thank you for the PR!

I've only had a brief look so far and left comments on a few obvious things I noticed here and there. Hopefully I'll be able to spend more time reviewing it in depth as time allows.

One major point I'm still unsure about is the GSC naming. For example, setanim feels very generic. I'll need to gather some additional opinions on that, especially since I'm not directly involved in these parts.

Overall, there isn't much more for me to add at the moment, but thanks again for the contribution 🫡

this was kept in here on accident but setanim is used to play inspects thru the means of iw4 weapon def anim indexing lol
@neoXclusive
Copy link
Author

Setanim was kept on accident and has been removed this isn't a needed function for VMFX 😎. Thanks for your time, hopefully antiga may be able to pop in and give a bit more insight soon.

Copy link
Contributor

@mjkzy mjkzy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool pr neo, i just dropped my 2 thoughts here if u wanted feedback :)

Game::CG_PlayBoltedEffect(0, fx, dobjHandle, tagName);
});

GSC::Script::AddFunction("PVMFX_BOTH", []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for what it's worth, this naming convention is really weird compared to the usual tradition of IW4x builtins and such. PVMFX_BOTH seems to do what PlayViewmodelFX but just plays on both hands instead, and instantly stops the bolt fx and playing it. i think the PVMFX_BOTH function could just be built into PlayViewmodelFX, and you can add a boolean 3rd (index 2) parameter for doing both hands, or if u need left hand only, just add a "hand index" parameter and then maybe both hands boolean still?

CG_WeaponDObjHandle_t CG_WeaponDObjHandle = reinterpret_cast<CG_WeaponDObjHandle_t>(0x41DB70);
CG_StopBoltedEffect_t CG_StopBoltedEffect = reinterpret_cast<CG_StopBoltedEffect_t>(0x44C230);


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redudant space here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants