NerdanelVampire: The most recent thing I've been doing with Zothiqband is a new variant of the random number generator. I wanted to make a rng that instead of always giving a number between, say, its input values 2 and 5 might indeed give a 7 or even (very, very rarely) a 500. I also made the likelihood of high numbers dependent on dungeon level.
So I started going through egos.lua and replacing calls to rng(x,y), but then I met the getters...
For example: STATS = obvious(getter.stats{[A_STR] = getter.random(1,3)})
My problem with getters is that I don't understand them. They appear to be part C, part Lua, which is a particularly confusing area. The really offending ones from engine/info.lua:
-- Random getter
getter.random = function(min, max)
-- Min must be first
if min > max then min, max = max, min end
return { [2] = min, [1] = max, flags = FLAG_FLAG_RAND }
end
-- Mbonus getter
getter.mbonus = function(min, max)
return { [2] = min, [1] = max, flags = FLAG_FLAG_MBONUS | FLAG_FLAG_RAND }
end
-- Random + mbonus getter
getter.rand_mbonus = function(rand, mbonus)
return { [2] = rand, [1] = mbonus, flags = FLAG_FLAG_RAND_MBONUS | FLAG_FLAG_RAND }
endIs getter.random interchangeable with the function rng? What's a mbonus? Is getter.mbonus and/or getter.rand_mbonus interchangeable with rng? Do some of those getters perhaps duplicate the intent of my new random number generator, making replacing them in my ego items pointless?
BucketMan: Let me first try to answer the questions, and then I'll offer a much easier way to do what you want.
In general, 'getters' are used for values attached to things (like items) that are defined during initial game load, but need to be random for each instance of the thing created. Reason being, if you use rng() it will generate a random value, and assign that random value when the file is loaded, rather than assigning a function that generates a random value every time the object itself is created. And, even worse, since the ToME random number generator is completely deterministic, that speed value will be exactly the same, even if you close and restart ToME. So...yes, you could give an item a flag like this:
flags =
{
SPEED = rng(1,7)
}
But, when items.lua was initially loaded, the rng() would execute then and there, pick a number, and assign that number then and there...and consequently every single one of those items ever created would always have exactly that much of a speed bonus. And, again, since the ToME random number generator is deterministic, every single time you started ToME, (thus re-loading the definitions file) you would still get exactly the same value for that item. If instead you did this:
flags =
{
SPEED = getter.random(1, 7)
}
getter.random() will assign an array to SPEED which contains "1" "7" and a flag that the item generation code knows to look for, indicating that it should generate a random value every time that object is created.
In fact, it should be possible to directly assign an array to an item flag for the c parser to deal with, without using a getter. function at all. At least, in theory. I think. I've never tried, but I would guess that:
flags =
{
SPEED = {1,7,FLAG_FLAG_RAND}
}
Would generate exactly the same result that this does:
flags =
{
SPEED = getter.random(1, 7)
}
When the item is created, the code sees the array and the flag, and knows to actually generate a random number and assign it. This is why you see so many different getter. functions. Different functions do different things. getter.random() is for simple random numbers. getter.resists() assigns a random resistance, to the appropriate position of the RESIST array. Everything that's stored differently needs its own getter. function. getter.mbonus(), I believe, generates the same random value that getter.random() does, but then checks for a material bonus on the item, and adds that to the final value.
Creating your own getter. functions might be possible, but since the item generation code itself needs to know how to handle the resultant array, it's not likely that you'll be able to create custom getter. functions that will actually be useful, and don't already exist.
To address what you actually want to do my advice would be not to use or worry about getter. functions at all. Instead, create a standard LUA function that generates the random number you want, and then use ON_MAKE to assign values to the item. For example:
function zothiq.rand(min, max)
local foo = rng(min, max)
local on_occassion = rng(1, 1000)
if on_occassion > 999 then
foo = foo + 500
elseif on_occassion > 990 then
foo = foo + 100
elseif on_occassion > 900 then
foo = foo + rng(min, max)
elseif on_occassion < 2 then
foo = foo - 500
elseif on_occassion < 11 then
foo = foo - 100
elseif on_occassion < 99 then
foo = foo - rng(min, max)
end
return foo
end
Then, add the following flag to the ego item you want to use it:
ON_MAKE =
function(obj)
obj.to_h = obj.to_h + zothiq_rng(1,15)
obj.to_d = obj.to_d + zothiq_rng(1,15)
end
NerdanelVampire: Thanks! That cleared things a lot!
For general interest, here is my (dumb and simple) random function. I tried directly replacing the rng() call with getter.random, but got an error message about performing arithmetic with a table value. I got around it however by moving the arithmetic inside the function call. This version appears to work; at least it doesn't cause errors on startup.
up_rng = function(a, b)
local step = b - a
local iter = 0
local chance = dun_level/5
-- Beware of infinite loops very deep down!
if chance > 80 then
chance = 80
end
while rng.percent(chance) do
iter = iter + 1
end
return getter.random(a + iter*step, b + iter*step)
endNow that I think of it, I still have the problem with the code being executed only once per line in egos.lua, though... I'll have to look into it tomorrow. It's getting way too late to think straight.
Now that I think of it (even more) I think I really want a custom getter. function. Considering that I'm planning to drop randomness all over the place in egos.lua, the repetitive ON_MAKE sections would proliferate beyond all reason. I'm going to dive into the getter code and possibly file a bug report.
A little later... I simply search-and-replaced all instances of up_rng with with getter.up_rng (and removed the now-unneeded global constant declaration). I get no error messages, but every weapon of slay animal I generate appears to be x3... It appears things aren't that easy.
BucketMan: I'm afraid I don't understand the above code. But if it helps: getter.random() does not return a random number. It returns a table that contains instructions on how to generate a random number. Instructions that will generally only be understood by the object generation code. For example:
message(getter.random(1,2))
will generate a LUA error. It would think it would be possible to use up.rng() in place of getter.random() is object definitions, but up.rng() contains a a regular rng(), and so is likely to generate deterministic values in the output function. Meaning your ranges will be the same in every game. I don't think that's what you want.
Derakon: Speaking as someone who hasn't looked at the ToME code at all, you probably don't want to replace the existing random number generator entirely. There are some things where you really do want to cap the numbers - for example, the hitpoints the player gains on levelup. Random numbers are also probably used for things like spell selection and character history generation, where if the number falls outside the expected bounds, the program will crash (You have a set of six possible different ancestors to choose from, so you call rng(1, 6) and get eight. You try to look up ancestor #8 and discover he was a chunk of random noise. Oops).
NerdanelVampire: Derakon, I was in no way intending to replace the standard rng everywhere, but have a different, differently-named rng in special cases like how well an ego item slays dragons or how well another ego item resists fire. (Nothing bad should happen if an item resists fire 105%, right?)
To BucketMan: I notice that I had left one non-getter rng in the code (it WAS hideously late/early in the morning)... Trying to replace the offending line with
while getter.random(1, 100) <= chance do
results in the old comparing table with a number error message. Unfortunately I see no way around it to make it work...
BucketMan: getter.random() does not return a number. It returns a table. Trying to perform logical operations on tables will generate errors, yes. What you're trying to do is exactly the same as:
while {1, 100, FLAG_FLAG_RAND} <= chance do
...and yes, that will generate an error. Look up above at the example using SPEED.
NerdanelVampire: I looked further into item creation, and the problem is that items are made in C code, to which my only access without forking the T-Engine is the ON_MAKE flag. However, if I understood this right, and I really hope I did, I can call the getter-less version of the random number generator in every ON_MAKE and get different random numbers for different version of the same ego item. (It better be that way, or my algorithm won't work.) What I really don't like this is how it removes the "meat" from the ego into hacky tacked-on sections. Apparently I won't even be able to use the standard "this feature is, say, 10% likely to be in the ego" sections and have to program them in by hand. That kind of code duplication is a breeding ground for stupid mistakes.
BucketMan: Personally I like ON_MAKE. It allows me to do everything I want to do, and that's far more important to me than coding aesthetics. Yes, using rng() in ON_MAKE should work. If you want to avoid code duplication, just create a function for ON_MAKE to call, like the example with zothiq_rand() above. That way one function can be used for as many or as few ego types as you want.
NerdanelVampire: So I started to do the egos the ON_MAKE way. It turned out that in certain ways ON_MAKE wasn't nearly as bad as I feared, but in other ways it was much worse.
ON_MAKE =
{
RESIST = obvious(getter.array{[dam.FIRE] = up_rng(60,70)})
BRAND = obvious(getter.array{[dam.FIRE] = up_rng(2,3)})
if rng.percent(10) then
{
SLAY = obvious(getter.array{[SLAY_UNDEAD] = up_rng(2,3)})
}
}
results in
error: <name>, `function' or `[' expected; last token read: `if' at line 28 in file `/data/items/egos.lua'
ON_MAKE =
{
RESIST = obvious(getter.array{[dam.FIRE] = up_rng(60,70)})
BRAND = obvious(getter.array{[dam.FIRE] = up_rng(2,3)})
[10]
{
SLAY = obvious(getter.array{[SLAY_UNDEAD] = up_rng(2,3)})
}
}
results in
error: attempt to call a nil value stack traceback: 1: main of file `/data/items/egos.lua' at line 31
What this means that there appears no sensible means of randomizing features that may or may not be present. I guess I could make different egos all named the same, and perhaps some function could be conceived that produces either nothing or a valid ON_MAKE line, but this behavior is definitely a bug.
NerdanelVampire: I've filed two ON_MAKE bugs today. In addition to the previous problem, it turns out that all slays and brands and resists (and an unknown number of things I haven't tested) simply get ignored if they are in an ON_MAKE section. I done tests and found out that this isn't just a bug in the wizard mode item creation commands, but ON_MAKE gets ignored in dungeon loot too.
NerdanelVampire: Well, well, well... My ON_MAKE bug report has been closed because DarkGod think there isn't a bug. He told me to use code like
ON_MAKE = function(obj)
-- code goes here
end(I also got some advice on the use of obj.flags{} but it doesn't matter right now.)
The problem is, my ON_MAKE code isn't getting executed AT ALL. I have proven this by adding debug printouts everywhere using the lua "print" command. (I know it works. I have used it a lot in debugging other things.) This should produce messages to the console, but I'm just not getting any, even when I find egos generated in the dungeon. Neither am I getting any of the flags I specified in the ON_MAKE functions.
I have requested reopening the bug report, but I think I could really use an another brain right now. Who is right? Me or DarkGod?
BucketMan: I don't use SLAY, and last I checked it was non-obvious how to get them to work in any except the most basic fashion...so I can't really help you much there. But I will vouch for the fact that ON_MAKE does work. If yours aren't executing at all, my first guess would be that there is a bug somewhere else that's preventing them from executing. T3 does this routinely. The first time it bit me, one of my town maps wouldn't load. The actual problem was that I had a monster death flag generating an invalid item, or something similarly unrelated. Why did this prevent towns from loading? Because there was a dungeon in the town...with a special level...with a monster...that when it died...generated that item. So the town wouldn't load. This was back around alpha 5 or so, but yes, T3 does sometimes crash and die for totally unexpected reasons because of problems that have absolutely nothing obviously to do with the symptoms you're seeing. And, when the actual problem doesn't generate an error message, it can result in countless hours of pain and misery debugging. Personally I simply don't get why the engine does some things the way it does. Sometimes it is absolutely obtuse. But on the bright side, once you've learned the engines quirks, you do start to see a pattern in the chaos. And when you don't, it's usually pretty willing to let you rip its guts out and put new ones in. In any case, does this problem your having manifest in the current public version of Zothiq? If you'd like I'll take a look and see if I can root it out.
NerdanelVampire: No need anymore... I have discovered the reason why things didn't work, but your comments were instrumental in leading me to the right path, even if you didn't know the solution yourself because they made me do more experiments with most of my dataset commented out.
It turns out that my (revised) ON_MAKE code was exactly right (or at least doing something), but it was in the wrong place. The code needs to be like this:
flags =
{
[100] = -- or whatever probability
{
ON_MAKE = function(obj)
-- code goes here
end
}
}
All this time working with the T-Engine, I had accustomed to hook things being on the side and not in the middle of things like that. I had thought I had to do the [50] = thing (or an equivalent thing with if and normal variables) inside the ON_MAKE block and not the other way around.
Now I'll need to redo the egos once again.
NerdanelVampire: I found a nasty little problem. When I change an object's flag with obj.flags it gets changed completely, even if it's an array, such as RESIST. The problem with this is, let's say we have an elemental scmitar of Naat. It looks pretty awesome, but surprise! It gives resistance to EITHER the four basic elements OR darkness and nether and not both, as you might think by knowing what the egos do alone. The ego that got added the last overwrites the resistances of the previous one.
That's pretty nasty. I don't know how to avoid that except to not allow that kind of weapons, regardless of how awesome they would be to find.
NerdanelVampire: So I tried to get creative. I though, what if I tried it like this...
RESIST = getter.array{[dam.FIRE] = max(up_rng(55,72), obj.flags[RESIST][dam.FIRE])}
The idea is that if an ego gives a value bigger than the
The game starts with no errors, but it turns out that you can't take max of a number and a nil value.
It would be so much easier if Lua didn't have a separate nil value. Well okay, not really easier, but at least what I wanted to could be done even if it would be fiendishly complicated and require every resistance to be mentioned in every single ego that does something to resistances, and the same for brands, slays, etc.
HOW ON EARTH can I add flags non-destructively?
Hm, perhaps I could write my own max operator... It's going to make egos.lua ugly and complicated, though... This is something that really should be handled in the engine, though. Hmmm... Perhaps I could write my own flag-adding function...
BucketMan: Ahh...yes. This is something that I've been trying to for about eight months now. I've not succeeded yet, but you might take a look at the studded and designer egos in Dragonball T. They might at least save you struggling with the first few steps of the process. Setting resists to zero does work, but they appear as zero on inspection, which looks bad. Extracting individual resistances, adding, and then reapplying also works, but it chokes and dies if they're nil. It should be trivial to check if it's nil, and simply use zero if it is...but for some reason I haven't been able to do that. Last I looked into it, I had pretty much resigned myself to creating if then blocks to handle each case for each resistance...but even that didn't work for some reason or another. You might also consider looking at the ToME on_enchant function, as it appears to do something similar to what we want when enchanting damages. Good luck to you. Let me know if you figure it out. I'll happily steal the solution for DBT.
NerdanelVampire: I've made some progress. I found out how to add non-destructively. The syntax took trial and error to figure out, but it works.
This is how to add a random fire resistance using my rng:
obj.flags[FLAG_RESIST][dam.FIRE] = up_rng(55,72)
The problem, though, is that FLAG_RESIST needs to be non-nil for it to work. If I for example wish for a fiery spear, the game crashes unless I change the code like this, for example:
obj.flags{RESIST = getter.array{[dam.COLD] = 0}}
obj.flags[FLAG_RESIST][dam.FIRE] = up_rng(55,72)This produces a weapon that grants both fire and cold resists. (0% for cold.) So...
obj.flags{RESIST = getter.array{[dam.FIRE] = 0}}
obj.flags[FLAG_RESIST][dam.FIRE] = up_rng(55,72)This is unelegant but it appears to work... somewhat. The problem with this example is that the first line nukes everything, soooo...
if not obj.flags[FLAG_RESIST] then
obj.flags{RESIST = getter.array{[dam.FIRE] = 0}}
end
obj.flags[FLAG_RESIST][dam.FIRE] = up_rng(55,72)This is even less elegant, but it should genuinely work. I would really appreciate a simpler way, though.
Also, I wrote a simple function to take the maximum of two values, discounting nils. (Fixed version.)
flag_max = function(a, b)
if a ~= nil then
if b ~= nil then
return max(a, b)
else
-- b == nil
return a
end
else
-- a == nil
return b
end
endIf I put a call to this everywhere, a fiery penknife of Hell should no longer produce less resistance than a plain penknife of Hell if "fiery" gets added after "of Hell".
NerdanelVampire: Things are getting better.
Here is the flags section of my fiery ego:
flags =
{
[100] =
{
LITE = obvious(1)
ON_MAKE = function(obj)
add_flag(obj, FLAG_RESIST, dam.FIRE, up_rng(55,72))
add_flag(obj, FLAG_BRAND, dam.FIRE, up_rng(2,3))
end
}
[10] =
{
ON_MAKE = function(obj)
add_flag(obj, FLAG_SLAY, SLAY_UNDEAD, up_rng(2,3))
end
}
}
It uses this handy function:
add_flag = function(obj, group, item, addition)
if not obj.flags[group] then
obj.flags[group] = getter.array{[item] = 0}
end
obj.flags[group][item] = max(addition, obj.flags[group][item])
endI also made two other helper functions, one to be used in cursed objects and one if I want to add pluses mathematically instead of just taking the higher number:
min_flag = function(obj, group, item, subtraction, know)
if not obj.flags[group] then
obj.flags[group] = getter.array{[item] = 0}
end
obj.flags[group][item] = min(subtraction, obj.flags[group][item])
end
plus_flag = function(obj, group, item, addition, know)
if not obj.flags[group] then
obj.flags[group] = getter.array{[item] = 0}
end
obj.flags[group][item] = addition + obj.flags[group][item]
endMy current problem is that I don't have any idea how to add "obvious" to that. Trying the naive way gave me the following error message:
error: attempt to index local `typ' (a nil value) stack traceback: 1: `settable' tag method at line 548 [file `/engine/util.lua'] 2: function `add_flag' at line 13 [file `/scripts/add_flag.lua'] 3: function <22:file `/data/items/egos.lua'> at line 23 4: function `apply_magic' [C] 5: function `func' at line 766 [file `/engine/wish.lua'] 6: function <782:file `/engine/wish.lua'> at line 814
NerdanelVampire: Scratch that sense of success. I'm no farther than yesterday minus some pretty encapsulation.
I'm starting to really HATE, HATE, HATE Lua's stupid nil. WHY can't I check if a variable is nil without the whole thing not going down in flames only if the variable in fact it IS nil, making the whole thing pointless? This causes every ego and ego combination that adds more than one value to a flag now just break!!! Before flags would just get overwritten quietly, but now I get errors, errors, errors! I HATE THAT! C++ IS SO MUCH BETTER!!!
THAT IDIOTIC NIL IS GETTING ME TO TYPE IN ALL CAPS AND ADD EXTRANEOUS EXCLAMATION MARKS!!!!!!
NerdanelVampire: I have finally SOLVED(!!!) the nil issue and all by myself.
I have to say the solution was really non-obvious although now I see it it makes (a little) sense. The weird thing is that normal numbers don't have that sort of troubles at all, only the userdata datatype.
Here is my new add_flag function:
add_flag = function(obj, group, item, addition)
if type(obj.flags[group]) == type(nil) then
obj.flags[group] = getter.array{[item] = 0}
end
if obj.flags[group][item] == nil then
obj.flags[group][item] = addition
else
obj.flags[group][item] = max(addition, obj.flags[group][item])
end
endGaze it and let it sink in.
BucketMan: Woohoo! It works! This has been giving me trouble for a long time. Thank you.
ToME Wiki