Modding Q&A thread
Fluid Core
Join Date: 2007-12-26 Member: 63260Members, Reinforced - Shadow
in Modding
<div class="IPBDescription">Sharing your collected knowledge</div>Hello Community!
Lately I've been thinking about creating some mod again, this time for the gorge. Before I've been wrestling on a skulk view rotation mod, but although we got both view rotating and inputs changing it never quite worked out for the general case. The thing is, I think we as a community have so much knowledge about how to code different things, and someone probably know how to make a particular thing that have been bothering you work. So...
The intent of this thread is dual: asking for things you need help with, and replying to things you can help out with. I suppose it could become a form of modding Q&A thread. I will try to add the questions and answers to the first post for ease of access.
<b>Q: How do you find the wall normal for a non-wallwalking situation? Either for rotation or collision purposes, such as bouncing.</b>
A: ???
<b>Q: How do you make an attack "charge up" as you hold the attack key? Think in the sense of TF2 sniper with the huntsman.</b>
A: ???
Lately I've been thinking about creating some mod again, this time for the gorge. Before I've been wrestling on a skulk view rotation mod, but although we got both view rotating and inputs changing it never quite worked out for the general case. The thing is, I think we as a community have so much knowledge about how to code different things, and someone probably know how to make a particular thing that have been bothering you work. So...
The intent of this thread is dual: asking for things you need help with, and replying to things you can help out with. I suppose it could become a form of modding Q&A thread. I will try to add the questions and answers to the first post for ease of access.
<b>Q: How do you find the wall normal for a non-wallwalking situation? Either for rotation or collision purposes, such as bouncing.</b>
A: ???
<b>Q: How do you make an attack "charge up" as you hold the attack key? Think in the sense of TF2 sniper with the huntsman.</b>
A: ???
Comments
You can have a look at the wall walking code in WallMovementMixin:GetAverageWallWalkingNormal but basically you do a trace with a given direction to see if you hit anything.
It's seems there is two kind of traces Shared.TraceCapsule and Shared.TraceRay, I'm not sure what's the difference, maybe TraceRay use only a line and TraceCapsule move the collision box.
Anyway you can use this as an example from GetAverageWallWalkingNormal, once you did the trace you just need to check if it hit anything.
startPoint is the start position of the ray and endPoint (startPoint + Vector(0, -wallWalkingRange, 0) in this case) the end of the ray.
groundTrace.fraction is the fraction of the length at which the ray touched something, so if you want the distance you need to do fraction * (endPoint-startPoint):GetLength(). groundTrace.normal is the normal of what you touched, I'm not sure about groundTrace.entity but it probably tells you if you hit a player or something else.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// Check if we are right above a surface we can stand on.
// Even if we are in "wall walking mode", we want it to look
// like it is standing on a surface if it is right above it.
local groundTrace = Shared.TraceRay(startPoint, startPoint + Vector(0, -wallWalkingRange, 0), CollisionRep.Move, PhysicsMask.AllButPCs, EntityFilterOne(self))
if (groundTrace.fraction > 0 and groundTrace.fraction < 1 and groundTrace.entity == nil) then
return groundTrace.normal
end<!--c2--></div><!--ec2-->
<i>But maybe I can use that by using the speed vector as the direction of the trace, and somehow making the fraction=1 the size of the model.</i>
To clearify what I'm going to try to do with the code, I plan on making the gorge belly slide bounce against walls (angle of reflection same as angle of inclination naturally) and also get a speed boost during the bounce.
For the charging up effect, I seek to make the spit chargable, dealing more damage and getting more mass the longer you hold. This way you can charge up your spits, dealing more damage but the range getting shorter. Once i got that working I can start tweaking splash effects and potential slowing (more with longer charge...)
I think the ray trace is the way to go for this, something like this :
if bellySliding then
do a trace in front of the gorge (in velocity or view direction)
if trace hit a wall then
flip velocity and add bounce
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> // Check for space
if structure:SpaceClearForEntity(coords.origin) then
[b] local angles = Angles()
angles:BuildFromCoords(coords)
structure:SetAngles(angles)[/b]
if structure.OnCreatedByGorge then
structure:OnCreatedByGorge(self.lastCreatedId)
end
player:AddResources(-cost)
if self:GetActiveStructure():GetStoreBuildId() then
self.lastCreatedId = structure:GetId()
end
// Jackpot
self.droppedStructure = true
player:SlowDown(1)
return true
else<!--c2--></div><!--ec2-->
It's in weapons/alien/DropStructureAbility.lua - it's clear that it does <i>something</i> with the angle... Just how it actually get it right is beyond me.
The coordinates system is create by GetPositionForStructure :
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// Given a gorge player's position and view angles, return a position and orientation
// for structure. Used to preview placement via a ghost structure and then to create it.
// Also returns bool if it's a valid position or not.
function DropStructureAbility:GetPositionForStructure(player)<!--c2--></div><!--ec2-->
If you look into it, it actually does a ray cast :
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> local origin = player:GetEyePos() + player:GetViewAngles():GetCoords().zAxis * range
// Trace short distance in front
local trace = Shared.TraceRay(player:GetEyePos(), origin, CollisionRep.Default, PhysicsMask.AllButPCsAndRagdolls, EntityFilterTwo(player, self))<!--c2--></div><!--ec2-->
I already used setDisruptionDuration( ) in order to stun the player but I'm attempting to make him fly across the room.
I added some lines of
self.amblight = Client.CreateRenderLight()
self.amblight:SetType( RenderLight.Type_Point )
self.amblight:SetColor( Color(255,106;34) )
and a couple other parameters, saved it and tried to test it ingame. But when I started a map it immediately kicked me saying that my Client differs from my Server, even though I just hosted one when I loaded my map.
Can anyone tell me why that is?
self.amblight:SetType( RenderLight.Type_Point )
self.amblight:SetColor( Color(255,106;34) )<!--QuoteEnd--></div><!--QuoteEEnd-->
Did you mistype a comma here Color(255,106<!--sizeo:5--><span style="font-size:18pt;line-height:100%"><!--/sizeo--><b>;</b><!--sizec--></span><!--/sizec-->34)
You also need to make sure you code is inside a "if(Client) then YOURCODE end" guard
Look at the console while it's loading. You can also start a dedicated server so you got a dos console to look for errors without having to start the game.
New problem though is when it is placing the lights, it is not next to the cyst. Although I imagine that is because I did not tell it to place the light at parent model location. Could someone tell me what type of strings I would need? Some kind of Get:SelfPos? Self:SetPos()?
entity:GetCoords() or entity:GetModelCoords() where entity is the parent cyst. Then light:SetCoords() to place, where the set coords match the entity coords.
Function names may be wrong, lack of practice breeds a lack of confidence :)
Look at the code for the current glow for the cysts as this is a light that emanates at the point of the cyst, failing that check out twiliteblue's code for the cyst light mod he has done :)
This puts a light on the alien commander's cursor, using 'SetCoords', which is probably what you'll to position it on cysts.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local mouseX, mouseY = Client.GetCursorPosScreen()
local pickVec = CreatePickRay(self, mouseX, mouseY)
local trace = Shared.TraceRay(self:GetOrigin(), self:GetOrigin() + pickVec*1000, CollisionRep.Select, PhysicsMask.CommanderSelect, EntityFilterOne(self))
self.cursorLight:SetCoords(Coords.GetTranslation(trace.endPoint + trace.normal * 0.5))<!--c2--></div><!--ec2-->
Well I was working a bit on the whole cyst lighting thing. I did actually manage to get a working version which you can use offline. But you can't join a server with this mod running, (client/server mismatch) so I was trying to figure out how to make it use Workshop. Note: A lot of the code was taken straight from twiliteblue's cyst mod.
What it looks like.
<a href="http://i.imgur.com/p0cL5.jpg" target="_blank"><img src="http://i.imgur.com/p0cL5l.jpg" border="0" class="linked-image" /></a>
Basically my problem stems as such:
1) I edited Cyst.lua
2) That won't work when joining servers, need workshop
3) Make a mod called CystLights, make lua file called CystLights.lua.
4) Added the code
5) Code is just the isolated parts of the changes I did to Cyst.lua
Now obviously, this isn't working like it was when I changed Cyst.lua, so my question is what kind of code I would need in order to integrate this with Cyst.lua? I was looking at 6john's FlashLite code to see how he does it, and it does appear he uses a Class_ReplaceMethod. Although to me, it just looks as though he changes some wording in the code instead of inserting it into the original. I did try adapting it to use the format in his code, but it still didn't work. So what is it that I am doing wrong?
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// CystLights mod
Script.Load("lua/Cyst.lua")
Cyst.kGlowIntensity = 2
Cyst.kGlowRadius = 12
Cyst.kGlowColor = Color(255/255, 128/255, 64/255)
function Cyst:OnDestroy()
if Client then
if self.glow ~= nil then
Client.DestroyRenderLight(self.glow)
end
end
end
function Cyst:OnInitialized()
if Server then
end
elseif Client then
// create the emissive light
self.glow = Client.CreateRenderLight()
self.glow:SetType( RenderLight.Type_Point )
self.glow:SetCastsShadows( true )
self.glowCoords = CopyCoords(self:GetCoords())
self.glowCoords.origin = self.glowCoords.origin + self.glowCoords.yAxis * 0.6
self.glow:SetCoords( self.glowCoords )
self.glow:SetRadius( Cyst.kGlowRadius )
self.glow:SetIntensity( Cyst.kGlowIntensity )
self.glow:SetColor( Cyst.kGlowColor )
self.glow:SetSpecular( true )
self.glow:SetIsVisible(true)
end
end<!--c2--></div><!--ec2-->
FlashLite
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local originalOnUpdateRender
originalOnUpdateRender = Class_ReplaceMethod( "Marine", "OnUpdateRender",
function(self)
originalOnUpdateRender(self)
if self.flashlightOn then
// Only display atmospherics for third person players.
local density = 0.1
if self:GetIsLocalPlayer() and not self:GetIsThirdPerson() then
density = 0
end
self.flashlight:SetAtmosphericDensity(density)
end
end
)<!--c2--></div><!--ec2-->
To get it on the workshop, create a new mod in launch pad, get all your files into that folder and once it's working, click publish from there. There's a guide at the top of this modding forum.
For a more advanced example look at fsfod's non-flash menu or Combat Mode, which are both using fsfod's "Class Hook" functionality and can make replacements at the beginning or end of functions without having to copy the whole function, making maintenance almost a non-issue for small projects.
I think it's about time someone wrote a decent 'how to start a mod' guide... I'll give it a go this week.
This is getting fairly frustrating considering I have to publish the mod, download the mod, load the map, load the map a second time, and THEN get my errors. Really wish the "test mod" button worked.
CystLights.lua
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Script.Load("lua/Class.lua")
Script.Load("lua/Cyst.lua")
local originalCystOnDestroy
originalCystOnDestroy = Class_ReplaceMethod("Cyst", "OnDestroy",
function(self)
if Client then
if self.glow ~= nil then
Client.DestroyRenderLight(self.glow)
end
end
originalCystOnDestroy(self)
end
)
local originalCystOnInitialized
originalCystOnInitialized = Class_ReplaceMethod("Cyst", "OnInitialized",
function(self)
if Server then
elseif Client then
// create the emissive light
self.glow = Client.CreateRenderLight()
self.glow:SetType( RenderLight.Type_Point )
self.glow:SetCastsShadows( true )
self.glowCoords = CopyCoords(self:GetCoords())
self.glowCoords.origin = self.glowCoords.origin + self.glowCoords.yAxis * 0.6
self.glow:SetCoords( self.glowCoords )
self.glow:SetRadius( Cyst.kGlowRadius )
self.glow:SetIntensity( Cyst.kGlowIntensity )
self.glow:SetColor( Cyst.kGlowColor )
self.glow:SetSpecular( true )
self.glow:SetIsVisible(true)
end
originalCystOnInitialized(self)
end
)<!--c2--></div><!--ec2-->
The Console Mess
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Downloading mods
Mounting mod from C:/Users/Chris/AppData/Roaming/Natural Selection 2/Workshop/m60a5889_1349683990
Mounting mod from C:/Users/Chris/AppData/Roaming/Natural Selection 2/Workshop/m60a5889_1349683990
Mounting mod from C:/Users/Chris/AppData/Roaming/Natural Selection 2/Workshop/m60a5889_1349683990
Script Error #1: lua/SleeperMixin.lua:11: attempt to call global 'CreateMixin' (a nil value)
Call stack:
#1: lua/SleeperMixin.lua:11
#2: Load [C]:-1
#3: lua/Cyst.lua:11
#4: Load [C]:-1
#5: lua/CystLights.lua:4
#6: Load [C]:-1
#7: lua/Client.lua:3
Script Error #2: lua/FireMixin.lua:10: attempt to call global 'CreateMixin' (a nil value)
Call stack:
#1: lua/FireMixin.lua:10
#2: Load [C]:-1
#3: lua/Cyst.lua:12
#4: Load [C]:-1
#5: lua/CystLights.lua:4
#6: Load [C]:-1
#7: lua/Client.lua:3
Script Error #3: lua/UmbraMixin.lua:14: attempt to call global 'CreateMixin' (a nil value)
Call stack:
#1: lua/UmbraMixin.lua:14
#2: Load [C]:-1
#3: lua/Cyst.lua:13
#4: Load [C]:-1
#5: lua/CystLights.lua:4
#6: Load [C]:-1
#7: lua/Client.lua:3<!--c2--></div><!--ec2-->
It proceeds for another 25 script errors before finally "loading" the map. Not that it means anything because the map doesn't load. If you want, I could PM you the entire console log, but it is fairly lengthy.
<b>A: </b>Just google it. I don't know what versions you're on, but here's a Blender plugin: <a href="http://colladablender.illusoft.com/cms/" target="_blank">http://colladablender.illusoft.com/cms/</a>
I thought this would be something relatively simple, but for whatever reason I can't seem to get it to work. Below is a chunk of code from a mod I'm working on that's supposed to damage marine buildings when they're touched by infestation. To do this, I'm adding a bit of code to the Infestation_Server's UpdateInfestation function. But when I run the game it says that 'Global Class_ReplaceMethod (a nil value)'. I'm assuming I'm missing something here, probably something small and/or noobish as programming mistakes tend to be, so could someone be so kind as to point out what that mistake could be?
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Script.Load("lua/Shared.lua")
Script.Load("lua/InfestationMod_Balance.lua")
Script.Load("lua/InfestationMod_ResGeneration.lua")
Script.Load("lua/Infestation.lua")
Script.Load("lua/InfestationMod_Cyst.lua")
if Server then
local originalUpdateInfestation
originalUpdateInfestation = Class_ReplaceMethod("Infestation_Server", "Infestation:UpdateInfestation", function(deltaTime)
Shared.Message("Inside InfestationMod_InfestationChanges.lua!")
originalUpdateInfestation(deltaTime)
//How large is the radius of infestation around the cyst?
local smallestRadius = self:GetRadius()
local biggestRadius = self.lastRadius or 0
if smallestRadius > biggestRadius then
smallestRadius, biggestRadius = biggestRadius, smallestRadius
end
local origin = self:GetOrigin()
local function getMarineStructures(ent)
return ent ~= nil and ent:GetTeamNumber() == kmarineTeamType
end
//Get list of marine buildings within that range
local structure = Shared.GetEntitiesWithTagInRange("class:Structure", origin, biggestRadius, getMarineStructures)
//When marine buildings are touched by infestation, damage them.
for index = 1, #structure do
Shared.Message("It works!")
local dotMarker = CreateEntity(DotMarker.kMapName, self:GetOrigin() + Vector(0, 0.2, 0), kAlienTeamType)
dotMarker:SetDamageType(kInfestationDamageType)
dotMarker:SetLifeTime(kInfestationDuration)
dotMarker:SetDamage(kInfestationDamage)
dotMarker:SetRadius(1)
dotMarker:SetDamageIntervall(kInfestationDotIntervall)
dotMarker:SetDotMarkerType(DotMarker.kType.Static)
dotMarker:SetTargetEffectName("bilebomb_onstructure")
dotMarker:SetDeathIconIndex(kDeathMessageIcon.BuildAbility)
dotMarker:SetOwner(self:GetOwner())
end
end)
end<!--c2--></div><!--ec2-->
EDIT:
Updated code and question details. Still doesn't work, but I'm at least getting an error now. (Albeit one that doesn't make any sense.)
One thing I would mention with you code.. It will also do DoT to marines in it's current format, if it was working, as players are also scriptactors....
A: I didn't add Script.Load("lua/Class.lua") to the top.
from NS2Utility.lua
<div class='codetop'>CODE</div><div class='codemain' style='height:200px;white-space:pre;overflow:auto'>if Server then
Script.Load("lua/NS2Utility_Server.lua")
end
if Client then
PrecacheAsset("ui/buildmenu.dds")
end</div>
<b>Q: Is there an api doc for the "behind the scenes" classes ?</b>
from Player_Server.lua
<div class='codetop'>CODE</div><div class='codemain' style='height:200px;white-space:pre;overflow:auto'>function Player:OnClientConnect(client)
self:SetRequestsScores(true)
self.clientIndex = client:GetId()
self.client = client
end</div>
from the name of the variable "client" I assume that this is a client object but I cant find any documentation about what functions are available in this object
There is no documentation for anything yet, so the only thing you can do is dig through the code to find functions and variables.