Nameless Voice on 18/5/2011 at 22:50
That's a good idea with the partial subproperty name matches.
I'd thought of writing a generic property setter/adjuster script before (and parts of one are in some scripts already), but I never got around to fully generalising those features out into a standalone script.
There's actually a really flexible script in NVScript for adjusting properties based on difficulty, which you might consider taking some ideas from.
Basically, it let you choose a property and a subproperty, and then use operations on them, e.g. +, -, /, * as well as the standard =. If I was going to make a property-setting script, I'd abstract out that functionality from the difficulty-based stuff and work with that. The design note for that script would look something like this:
NVDifficultyProp1="HitPoints"; NVDifficultyOperator1="*"; NVDifficultyOperand1="1.3"; NVDifficultyProp2="MAX_HP"; NVDifficultyOperator2="*"; NVDifficultyOperand2="1.3";
Edit: Looking at the code now, it should be really easy to make a generic script out of this. I don't have the time right now, but I might look into it next week. Sorry for derailing your thread...
LarryG on 19/5/2011 at 04:11
Quote Posted by Telliamed
Can do.
Thank you.
The Watcher on 19/5/2011 at 22:35
Quote Posted by LarryG
Do you have a recommended IDE for Lua?
That's Holy Wars territory, that one.
(emacs! :devil:)
Nameless Voice on 19/5/2011 at 23:02
Is there seriously something simple that Notepad++ isn't the best editor for?
Telliamed on 20/5/2011 at 23:26
Well, that was annoying. Seems that emitted AI don't get the
Firer link like they're supposed to. Or rather, they do but it disappears in some mysterious way that I can't track because it doesn't fire the listener callback! So I have to resort to manually adding the link.
And I can't access my FTP at the moment, so I'll just pastbin it.
(
http://pastebin.com/YHSZzmyp)
(
http://pastebin.com/fR6CNvAS)
Yes, "FireShadowFlee", not "FireShadowFree". And the old description wasn't very accurate. The TimeWarp property is a motion time scale, not speed. So the value has to be decreased to make it go faster. I couldn't remember what the actual values were so I made something up that is close enough. And instead of manually checking if the spawn point is visible, just set the flicker tweq to "Offscreen". You just need to make the trap have a RenderType. (Follow the property description in the comments.)
Quote Posted by Nameless Voice
I'd thought of writing a generic property setter/adjuster script before (and parts of one are in some scripts already), but I never got around to fully generalising those features out into a standalone script.
Well, the simple way would be something like this, which just does the same thing as the "Set/Clone/Remove Property" receptron (well, except for clone, but that's trivial enough... actually I was a bit surprised to see what that one really does. It should more accurately be called "Clone Object")
TrapProperty.lua[code=lua]local pairs,sub=pairs,string.sub
local linksrv,propsrv,objsrv=lgs.LinkSrv,lgs.PropertySrv,lgs.ObjectSrv
local function ScriptParams(func)
for id,obj in pairs(linksrv.GetAll("ScriptParams", script.objid, 0)) do
local name = linksrv.LinkGetData(id) or ''
if name ~= '' then
func(obj,name)
end
end
end
function TurnOn()
ScriptParams(function(obj,name)
if sub(name,1,1) == '@' then
--[[ I don't have IObjectSystem::CloneObject (yet)
local from = objsrv.Named(sub(name,2))
if from then
-- This is like Create(from), but instead of creating a new object,
-- it applies the properties, metaprops, receptrons, etc. to an
-- already existing object.
objsrv.CloneObject(obj, from)
end
else
if propsrv.Possessed(script.objid, name) then
propsrv.CopyFrom(obj, name, script.objid)
else
propsrv.Add(obj, name)
end
end
end)
end
function TurnOff()
ScriptParams(function(obj,name)
if sub(name,1,1) ~= '@' then
propsrv.Remove(obj, name)
end
end)
end
For the more general script (which I'll probably just call "PropertyScript"), I think I'll go with the designnote param I mentioned before: fields="field1,field2";onvalue="value1,value2";offvalue="(),value2"
Parenthesis are needed for vector values since you need to commas in the value. But any value can have parenthesis which would mean "don't change". A blank value is just an empty string.
Fields are named with fuzzy-matching as so: Take the list of fields in the property, ignore anything that is not a letter or number, pick the first field that starts with the given name. After the name you can put a number in square-brackets, then pick the n-th field that matches the name. This is because some properties (AI responses, among others) have more than one field with the same name.
So if you want to modify a conversation, you could have a design note with:
property=AI_Converation;fields="ConversationAction2,argument1[3]"
If you look at the property in Dromed, you'll see that the fields are actually named "Conversation: Action 2" and the "Argument 1" field has spaces in front of it, and also appears on multiple lines. So adding the subscript makes the script skip the first two fields with the same name. Or you could even do the same thing with "Arg[7]" since it's just a prefix match.
If the property is a single field, you can ignore the list of names and just give a value. For that matter, without names for a complex property the script will just fill the fields in order with as many values as you give. This is simpler than Dromed's parser which uses curly braces and semicolons. Dromed also only recognizes the names of enumerated fields. I'd rather allow names or numbers.
I'll just have to make sure I completely parse the design note first in case someone tries to do something like "property=designnote;" and links the script to itself. (How bad would it be if you did that with "property=scripts"?)
LarryG on 21/5/2011 at 03:06
I've read it through once, and am working it through with FireShadowFlee
Quote Posted by Telliamed
Those scripts will be included in PublicScripts. It's just easier and faster to try it out in Lua first. That took about 6 hours and half of that was figuring out why the Firer link wasn't working. (plus twenty minutes to track down a bug in darkhooks.)
Ah! I wondered if I had misunderstood you before when I thought you would be doing so.
Quote Posted by Telliamed
T
*edit* I'm also thinking of a different way to dispatch timed messages. Each message is delivered as a
Timer message with a specific name. So you need a function named "Timer" that tests the name. Wouldn't it be easier to deliver the message to a function with the specific name instead? The message name would still be "Timer" so the function can tell if it's being called with a regular message or a timed message. There's just the concern that it would be too confusing since the same message name would come from two different events.
I'm not certain I understand that yet ... the function named "Timer" would essentially be a what is known in OOP as a method of the script object?
Here is what I have learned so far from the FireShadowFlee script, the only really puzzling bit for me is the missing LinkGet service. I really appreciate your posting these non-tivial Lua/lgs examples!
Code:
--[[
FireShadow {
Game\DamageModel\SlayResult = NoEffect
AI\AbilitySettings\FleeCondition = { OnLowHitpoints; 0; 100; }
Links\CorpsePart: FireCrystalPhys = Generic
Scripts = FireShadowFlee; SlayHaltSpeech
}
Metaproperty\M-FireShadowFlee {
AI\AbilitySettings\CombatNonhostile = AlwaysToPlayer
}
]]
-- Declare local constants: aliases for lgs services used by this script
local objsrv,linksrv,propsrv = lgs.ObjectSrv,lgs.LinkSrv,lgs.PropertySrv
-- Declare local constant:
-- object id of the M-FireShadowFlee metaproperty in the gamesys, = nil if none exists
local M_FireShadowFlee = objsrv.Named "M-FireShadowFlee"
-- Function to be executed when a "Slain" message is received by the script host object
function Slain()
-- create an objects of the type linked to with a CorpsePart link and drop them in place:
-- loop through the links looking for CorpsePart links from the script host
for l in pairs(linksrv.GetAllInheritedSingle("CorpsePart", script.objid)) do
-- found a CorpsePart link, get the object id of the object linked to
-- **** NOTE: the LinkGet service is not documented as a service of LinkSrv (http://dromed.whoopdedo.org/lgscript/linksrv) ****
-- **** but it is documented as a service of LinkToolsSrv (http://dromed.whoopdedo.org/lgscript/linktoolssrv) ****
-- **** need to check which is correct .......... ****
local drop = linksrv.LinkGet(l)
-- emit an object of that type with zero velocity and drop it
lgs.PhysSrv.LaunchProjectile(script.objid, drop, 0, "pushout,gravity")
end
-- if there is a metaproperty named M_FireShadowFlee in the gamesys (i.e. not nil)
if M_FireShadowFlee then
-- then add it to the script host
objsrv.AddMetaProperty(script.objid, M_FireShadowFlee)
end
-- add the TimeWarp property to the script host
propsrv.Add(script.objid, "TimeWarp")
-- set it to 13/16ths
propsrv.Set(script.objid, "TimeWarp", 13/16)
-- send the message "FireShadowFlee" to the script host object every second from now on
script:SetTimedMessage("FireShadowFlee", 1000, "periodic")
end
-- Function to be executed whenever a message is received by the script host object from SetTimedMessage
function Timer(msg)
-- is this a "FireShadowFlee" message
if msg.name == "FireShadowFlee" then
-- Yes, get the script host object's TimeWarp property value
local speed = propsrv.Get(script.objid, "TimeWarp")
-- caluclate a faster TimeWarp value
speed = speed * 0.8125
-- If too fast or the script host object is not on screen, destroy it
if speed < 0.03 or not objsrv.RenderedThisFrame(script.objid) then
objsrv.Destroy(script.objid)
-- otherwise set the TimeWarp property value to the new setting
else
propsrv.Set(script.objid, "TimeWarp", speed)
end
end
end
* * * * * * * * * * * * * * * *
EDIT: This is what I have learned from the FireEcology code. I have a few questions about this one.
First it looks like there should be a CD link from the FireSpawnPoint marker to the FireShadow archetype. Is that right? It's missing from the setup comments you provided.
Secondly, how exactly does the following code segment work,
[INDENT] local cd = linksrv.GetAllInheritedSingle("ControlDevice", script.objid)
local _,arc = cd(nil)[/INDENT]
It looks like you are setting an alias for the GetAllInheritedSingle service (or is the term
iterator?) with the first two parms specified ... is that right? and then you call the service with the 3rd parm set to nil, that would be the destination for the CD link. So is this returning the archetype id of the first or the last CD link in the set... ?
Thirdly, the code fragment
[INDENT]lgs.SoundSrv.Play(script.objid, "Event Launch", script.objid)[/INDENT]
seems to require that there be an "Event Launch" schema for the FireSpawnPoint object, is that right?
Finally, does the the code fragment
[INDENT]print(obj)[/INDENT]
write the new FireShadow's id to monolog?
Code:
--[[
FireSpawnPoint {
Tweq\Flicker = { Continue; NoLimit,Offscreen; nil; nil; 15000; }
Tweq\FlickerState = { On; nil; 14999; 0; }
Renderer\RenderType = Normal
Renderer\Has Refs = True
Shape\Scale = (0,0,0)
Scripts = FireShadowEcology
*** Start Missing ?? ***
Links\ControlDevice: FireShadow = Generic
*** End Missing ?? ***
}
]]
-- Declare local constants: aliases for lgs services used by this script
local objsrv,linksrv = lgs.ObjectSrv,lgs.LinkSrv
-- Function to "Emit" a new FireShadow at the current location
local function Emit()
-- get the achetype id of the (first? last?) achetype CD linked to from the script host
local cd = linksrv.GetAllInheritedSingle("ControlDevice", script.objid)
local _,arc = cd(nil)
-- any CD links?
if arc then
-- make a new concrete object of the indicated archetype at 0,0,0
local obj = objsrv.BeginCreate(arc)
-- move it to the location of the script host
objsrv.Teleport(obj, script.objid)
-- Finish the creation
objsrv.EndCreate(obj)
-- in a 1/10th of a second send the message "Firer" with the object id of the new FireShadow
script:SetTimedMessage("Firer", 100, 'oneshot', obj)
-- play the schema tag "Event Launch" found on the script host at its location
-- so this needs a schema ??
lgs.SoundSrv.Play(script.objid, "Event Launch", script.objid)
-- print out the object id of the new Fireshadow to Monolog (?)
print(obj)
end
end
-- Function to be executed whenever a message is received by the script host object from SetTimedMessage
function Timer(msg)
-- is this a "Firer" message?
if msg.name == "Firer" then
-- create a Firer link from the new FireShadow to the script host
linksrv.Create("Firer", msg.data[1], script.objid)
end
end
-- Function to be executed whenever a "TweqComplete" message is received by the script
function TweqComplete(msg)
-- is this a from a Flicker tweq?
if msg.Type == 6 and msg.Op == 5 then
-- Any Firer links?
if not linksrv.AnyExist("Firer",0,script.objid) then
-- no, so emit a FireShadow
Emit()
end
end
end
* * * * * * * * * * *
EDIT 2: For those interested, here is the (
ftp://ftp.inf.puc-rio.br/pub/docs/techreports/03_14_ierusalimschy.pdf) Lua 5.0 Reference Manual
Telliamed on 21/5/2011 at 21:04
Quote Posted by Nameless Voice
Having a script unload or reload itself mid-function definitely sounds like a bad idea.
Actually, I tried it and it works. For once Dromed doesn't crash when you do something unusual. The script manager knows not to delete the scripts while it's still processing a message. I guess it has to, since this isn't any different than destroying the object inside a script.
Quote Posted by LarryG
I'm not certain I understand that yet ... the function named "Timer" would essentially be a what is known in OOP as a method of the script object?
Yes. Under the proposed system the dispatcher would check the timer name for you and call the appropriate function.
The wiki is out of date. I merged LinkToolsSrv into LinkSrv. The functions are the same. Eventually I will make links and objects have their own methods, so instead of using the service you could do "local L = lgs.LinkSrv.GetOne(...) print(L:Dest())" ... or maybe I should use properties instead, "L.dest".
Quote:
First it looks like there should be a CD link from the FireSpawnPoint marker to the FireShadow archetype. Is that right? It's missing from the setup comments you provided.
Correct.
Quote:
Secondly, how exactly does the following code segment work,
[INDENT] local cd = linksrv.GetAllInheritedSingle("ControlDevice", script.objid)
local _,arc = cd(nil)[/INDENT]
It looks like you are setting an alias for the GetAllInheritedSingle service (or is the term
iterator?) with the first two parms specified ... is that right? and then you call the service with the 3rd parm set to nil, that would be the destination for the CD link. So is this returning the archetype id of the first or the last CD link in the set... ?
The LinkSrv.GetAll* functions return a LinkSet object, which can act as an iterator in ''for'' loops, but can also be manipulated directly. I only want one link so that's what I do. (The ''nil'' is there because of a bug, it's supposed to be optional.) The return values are (linkid, dest, source) but I'm only saving the dest object.
Quote:
Thirdly, the code fragment
[INDENT]lgs.SoundSrv.Play(script.objid, "Event Launch", script.objid)[/INDENT]
seems to require that there be an "Event Launch" schema for the FireSpawnPoint object, is that right?
"Play" uses schema tags. "PlaySchema" uses schema objects. When you play a tag, the object's Schema\Class Tags property gets added to it (and some other interesting tags, like the mission number)
Quote:
Finally, does the the code fragment
[INDENT]print(obj)[/INDENT]
write the new FireShadow's id to monolog?
Oh, did I forget to delete that? Yes, very helpful for testing.
LgScript uses Lua 5.1. (
http://www.lua.org/manual/5.1/manual.html)