|
Thread: Random Mixed Neutrals | |
|
Asheera
Honorable
Undefeatable Hero
Elite Assassin
|
posted August 26, 2008 11:42 PM |
|
Edited by Asheera at 12:00, 27 Aug 2008.
|
Random Mixed Neutrals
Hello,
In this thread I'll explain how to add random mixed neutrals on the map.
You will probably say: But there is a way to create mixed neutrals on the map. Yes, there is. But it's not random.
Still not get it? Well, here's the difference: You can place Peasants on the map, but it's better to place a random tier 1 creature so that it will be a new creature every time you play the map (much more interesting). It is the same with my function. It allows you to place random mixed neutrals on the map, not only fixed ones that we're able to place until now.
First of all, Copy & Paste the following function at the start of the map script (my creation)
function CreateCreepsAuto(creeps, creep_groups)
-- first we need the power table
local power_table = {41, 72, 140, 199, 201, 287, 524, 716, 1086, 1487, 2185, 2520, 4866, 6153, 75, 124, 101, 150, 259, 370, 511, 694, 1069, 1415, 2102, 2360, 4868, 5850, 54, 84, 105, 150, 232, 327, 518, 739, 1166, 1539, 2204, 2588, 3174, 3905, 100, 169, 191, 311, 309, 433, 635, 846, 1072, 1441, 1717, 1993, 4942, 6028, 63, 105, 113, 172, 243, 357, 498, 643, 839, 1126, 2108, 2535, 4822, 6095, 180, 295, 333, 484, 342, 474, 598, 812, 968, 1324, 2193, 2537, 5234, 6443, 829, 795, 856, 813, 2560, 8576, 70, 115, 115, 171, 304, 419, 318, 434, 932, 1308, 2109, 2477, 4883, 6100, 72, 203, 299, 697, 1523, 2520, 6003, 355, 671, 2523, 1542, 42, 69, 121, 174, 190, 254, 492, 680, 695, 926, 2058, 2571, 4790, 5937, 127, 149, 338, 680, 1434, 2448, 5860, 290, 477, 488, 833, 1333, 2622, 6389, 174, 308, 447, 862, 1457, 2032, 5905, 85, 145, 331, 757, 1541, 2449, 3872, 105, 180, 355, 642, 1096, 2581, 6095, 113, 171, 422, 434, 1329, 2437, 6070, 66, 181, 265, 692, 895, 2572, 5937}
local additional_stacks = {}
local x = 1
-- go through all creeps
for i, creep in creeps do
-- get current creep properties
local creep_name = "CreepAuto_" .. i
local group = creep_groups[creep.group]
local power = group.MinPower+random(group.MaxPower-group.MinPower+1)
local subgroup = group.CreatureIDs[random(length(group.CreatureIDs))+1]
local numCreatures = length(subgroup)
-- define number of stacks (max three) and the creatures' power divided between them
local c1 = random(numCreatures)+1
local c2 = 0
local c3 = 0
local c1_amount = 15+random(86)
local c2_amount
local c3_amount
if c1_amount > 85 then
c1_amount = 100
else
c2 = random(numCreatures-1)+1
if c2 >= c1 then c2 = c2 + 1 end
c2_amount = 15+random(86-c1_amount)
if c1_amount + c2_amount > 85 then
c2_amount = 100 - c1_amount
else
c3 = random(numCreatures-2)+1
if c3 >= c1 then c3 = c3 + 1 end
if c3 >= c2 then c3 = c3 + 1 end
c3_amount = 100 - c1_amount - c2_amount
end
end
-- convert the list IDs to creature IDs
c1 = subgroup[c1]
-- create the monster on the map with the first stack
CreateMonster(creep_name, c1, ceil(((c1_amount * power) * 0.01) / power_table[c1]), creep.x, creep.y, creep.u or 0, creep.mood or 1, creep.courage or 2, creep.rot)
if c2 > 0 then
c2 = subgroup[c2]
additional_stacks[x] = {Name = creep_name, Id = c2, Num = ceil(((c2_amount * power) * 0.01) / power_table[c2])}
x = x + 1
if c3 > 0 then
c3 = subgroup[c3]
additional_stacks[x] = {Name = creep_name, Id = c3, Num = ceil(((c3_amount * power) * 0.01) / power_table[c3])}
x = x + 1
end
end
end
-- pause the script so the engine can place the neutrals on the map
sleep(2)
-- add additional stacks (NOTE: only the first stack's size is adjusted by the difficulty of the game, we need to do it here manually)
x = GetDifficulty(); local y = 1.0
if x == 0 then y = 0.75
elseif x == 2 then y = 1.15
elseif x == 3 then y = 1.35
end
for i, v in additional_stacks do
AddObjectCreatures(v.Name, v.Id, v.Num*y)
end
end
This function will be used to create random neutrals on the map, with the possibility to be mixed as well. Note that it generates a neutral monster with either 1, 2 or 3 stacks, chosen randomly. (yes, only 3 max for now)
How to use the function
Firstly, you'll need to know the Creature IDs. They are found in the "HOMM5_A2_IDs_for_Scripts.pdf" file located in the Editor Documentation folder which is located where you installed TotE.
The first step is to place the Map Neutrals. Let's consider a new map and you want to add neutrals. Place a normal Random Neutral on the map where you want the random mixed stack, copy its position and place it in the MapNeutrals table. Something like this: (an example -> 5 and 76 are some arbitrary numbers)
MapNeutrals
{
[1] = {group = 1, x = 5, y = 76, rot = 90},
}
The rot value is the creep's orientation in degrees. Note that in the editor you have it in radians, and you'll need to convert it (multiply the radians by 57.295779513082) and then write it in the script. Also add a new value (u=1) if the stack is underground. After you're done adding the data to the script, delete the creep from the map.
The "group" is defined in another table, NeutralGroups. For example, the "normal" default neutrals have 7 groups - for each tier (you know, level 1 creep, level 5 creep, etc). My system is much more flexible, as you can add an unlimited number of groups, not to mention mixed stacks.
Let's consider that you add another neutral with the same group on the map in underground. It should look like this:
MapNeutrals
{
[1] = {group = 1, x = 5, y = 76, rot = 90},
[2] = {group = 1, x = 54, y = 46, rot = 180, u = 1},
}
Yes, as you can see, the presence of a "u = 1" variable marks it as in underground.
Now, we'll have to create the table holding the neutral groups. Here's an example (don't let it overwhelm you, I'll explain)
NeutralGroups =
{
[1] =
{
MinPower = 2000,
MaxPower = 2600,
CreatureIDs =
{
[1] = {1, 2, 106, 3, 4, 107},
[2] = {15, 16, 131, 17, 18, 132},
}
},
[2] =
{
MinPower = 3000,
MaxPower = 4000,
CreatureIDs =
{
[1] = {1, 2, 106, 3, 4, 107, 5, 6, 108},
[2] = {15, 16, 131, 17, 18, 132, 19, 20, 133},
}
},
[3] =
{
MinPower = 4200,
MaxPower = 5500,
CreatureIDs =
{
[1] = {2, 106, 3, 4, 107, 5, 6, 108, 7, 8, 109},
[2] = {16, 131, 17, 18, 132, 19, 20, 133, 21, 22, 134},
}
},
[4] =
{
MinPower = 6500,
MaxPower = 8800,
CreatureIDs =
{
[1] = {2, 106, 3, 4, 107, 5, 6, 108, 7, 8, 109, 9, 10, 110},
[2] = {17, 18, 132, 19, 20, 133, 21, 22, 134, 23, 24, 135},
}
},
[5] =
{
MinPower = 10000,
MaxPower = 13000,
CreatureIDs =
{
[1] = {9, 10, 110, 11, 12, 111},
[2] = {23, 24, 135, 25, 26, 136},
}
},
[6] =
{
MinPower = 15000,
MaxPower = 20000,
CreatureIDs =
{
[1] = {9, 10, 110, 11, 12, 111, 13, 14, 112},
[2] = {23, 24, 135, 25, 26, 136, 27, 28, 137},
}
},
[7] =
{
MinPower = 25000,
MaxPower = 33000,
CreatureIDs =
{
[1] = {9, 10, 110, 11, 12, 111, 13, 14, 112},
[2] = {23, 24, 135, 25, 26, 136, 27, 28, 137},
}
},
}
As you see, the NeutralGroups table contains a list of groups. Let's analyze group 1:
[1] =
{
MinPower = 2000,
MaxPower = 2600,
CreatureIDs =
{
[1] = {1, 2, 106, 3, 4, 107},
[2] = {15, 16, 131, 17, 18, 132},
}
},
A group contains the following properties:
MinPower and MaxPower are used to describe the neutral's power (it's measured in creature power values - you can check these in the Fan Manual). The function will pick a random number between these two values and use it to generate the neutrals as close to that power as possible.
CreatureIDs is a list of neutral "teams". The function will only choose and mix neutrals from only one team (randomly chosen for each neutral on the map). This is useful, for example, if you don't want to mix "good" and "evil" races together (you can create two teams: one holding all good creature IDs, while the other all the evil creature IDs). This is also used to filter some creatures for some groups if you want (for example, you don't want Peasants (in very large numbers) to be possible to spawn at a tier 7 neutral). Moreover, you can create only one team with all the neutrals and it will be purely random and chaos in creature selection. Or one team with only some creatures (for example, the Haven ones) so that the monster will only spawn between those creatures.
NOTE: In a team there must be at least THREE creature IDs, otherwise the script may fail at times!
Let's analyze the above example (group 1). It generates neutrals with a power between 2000 and 2600, chosen randomly. The creatures that are available to spawn and mix are either:
1) Peasants, Conscripts, Brutes, Archers, Marksmen and Crossbowmen
or
2) Imps, Familiars, Vermin, Horned Demons, Horned Overseers and Horned Grunts
I hope it's clear enough.
And finally, call the function and pass the above tables as parameters:
CreateCreepsAuto(MapNeutrals, NeutralGroups)
For more details, visit this thread. There you'll also find another function which is much more flexible in neutral generation, but it's also much more tedious to create neutrals with it. Use it if you want some "special" neutrals on the map, but it's really a long and boring job to create ALL neutrals with it.
NOTE: it takes some time to generate the neutrals at the start of the game (if there are a lot) - please be patient and wait until you can enter the town - that's a sign that the neutrals have been successfully added
____________
|
|
Asheera
Honorable
Undefeatable Hero
Elite Assassin
|
posted August 27, 2008 12:01 PM |
|
|
IMPORTANT
I have updated the function and fixed a bug. Please re-copy and paste it if you have already done so before.
____________
|
|
Warmonger
Promising
Legendary Hero
fallen artist
|
posted August 31, 2008 08:13 AM |
|
|
Hey Asheera, I was trying to use a table to add custom pandora boxes to my map. Here is my first attempt:
-- custom treasure chests
SetObjectEnabled ("Pandora1", false);
pandoraattack = {0};
pandoradefense = {0};
pandorapower = {2};
pandoraknowledge = {0};
-- pandora boxes
Trigger (OBJECT_TOUCH_TRIGGER,"Pandora1", pandora(heroName,1);
function pandora (heroName, index)
local att = pandoraattack["index"];
local def = pandoradefense["index"];
local sp = pandorapower["index"];
local knw = pandoraknowledge["index"];
ChangeHeroStat (heroName, STAT_ATTACK, att);
ChangeHeroStat (heroName, STAT_DEFENCE, def);
ChangeHeroStat (heroName, STAT_SPELL_POWER, sp);
ChangeHeroStat (heroName, STAT_KNOWLEDGE, knw);
RemoveObject ("Pandora"..index);
end;
let me know if I understood how do tables work.
|
|
Asheera
Honorable
Undefeatable Hero
Elite Assassin
|
posted September 02, 2008 12:47 PM |
|
|
You got it almost right, only that you shouldn't have used Quotation marks in the brackets (e.g. something like: pandoraattack[index])
However, there is a big problem with your script, and not related to tables, but to the Trigger function. You see, when the first parameter of the Trigger function is OBJECT_TOUCH_TRIGGER, you should pass two more parameters to this function: the name of the object to touch (in our case, "Pandora1", like you did) and the name of the function to trigger (in our case, it should be "pandora"). Unfortunately, as you can see, you can't pass parameters to this function manually yourself, but the Trigger function will pass parameters automatically. In the case of an OBJECT_TOUCH_TRIGGER, it will pass first the Hero name and then the object name. Thus in our example, it will pass the hero name and the second parameter will be "Pandora1" and not a value of 1 so that you can access tables
____________
|
|
Warmonger
Promising
Legendary Hero
fallen artist
|
posted September 09, 2008 10:09 AM |
|
|
After some trials I finally came up with a writer-friendly script which does its job and works flawlessly.
pandoraattack ={2,0,0};
pandoradefense ={0,2,0};
pandorapower ={0,0,2};
pandoraknowledge={0,0,1};
pandoracount = length (pandoraattack);
for i = 1, pandoracount do
SetObjectEnabled("Pandora"..i, nil);
SetDisabledObjectMode("Pandora"..i, DISABLED_INTERACT);
SetTrigger (OBJECT_TOUCH_TRIGGER,"Pandora"..i, "pandora"..i); -- can't pass function parameter
end;
function pandora1 (heroName) pandora (heroName,1); end; --how to put it into loop?
function pandora2 (heroName) pandora (heroName,2); end;
function pandora3 (heroName) pandora (heroName,3); end;
function pandora(heroName,index)
ChangeHeroStat (heroName, STAT_ATTACK, pandoraattack[index]);
ChangeHeroStat (heroName, STAT_DEFENCE, pandoradefense[index]);
ChangeHeroStat (heroName, STAT_SPELL_POWER, pandorapower[index]);
ChangeHeroStat (heroName, STAT_KNOWLEDGE, pandoraknowledge[index]);
RemoveObject ("Pandora"..index);
end;
Luckily I was able to customise map objects with a loop, only thing I'd like to fix is put there also function declaration so I'd not have to copy/paste function pandora1 (heroName) pandora (heroName,1); over and over again. I suppose it may be possible though I couldn't find any info about it today.
However, the current result is pretty acceptable when I look back at my previous map
|
|
Asheera
Honorable
Undefeatable Hero
Elite Assassin
|
posted September 09, 2008 01:06 PM |
|
|
Excellent idea!
Although I don't think it's possible to place those functions inside a loop.
____________
|
|
Warmonger
Promising
Legendary Hero
fallen artist
|
posted September 14, 2008 09:47 AM |
|
|
One quick question:
How to merge content of separate tables in order to create big one?
|
|
Asheera
Honorable
Undefeatable Hero
Elite Assassin
|
posted September 14, 2008 02:14 PM |
|
|
I'm not sure I understand what you mean
If you want to merge the following four tables into one:
pandoraattack ={2,0,0};
pandoradefense ={0,2,0};
pandorapower ={0,0,2};
pandoraknowledge={0,0,1};
Just create four sub-tables in one big table like this:
pandora_table =
{
attack = {2, 0, 0},
defense = {0, 2, 0},
power = {0, 0, 2},
knowledge = {0, 0, 1},
}
And when you want to access pandoraattack[index], for example, write something like:
pandora_table.attack[index]
Or you want something else?
____________
|
|
Warmonger
Promising
Legendary Hero
fallen artist
|
posted September 14, 2008 02:40 PM |
|
|
This will be very useful, but actually I meant adding the content of one table to another, so I'd get a new table containing itemns of them both.
These tables contain names of objects and heroes, so sometimes I want to refer to all of them (for example to disable AI for all heroes) or part of them (to level up only a group).
|
|
Asheera
Honorable
Undefeatable Hero
Elite Assassin
|
posted September 14, 2008 02:49 PM |
|
|
You want it to merge into one big table without any kind of "separation" between objects and heroes, or not?
With separation I mean something like:
Big_Table =
{
Heroes = {blabla, blabla},
Objects = {blabla, blabla},
}
Without separation something like:
Table = {blabla, blabla, blabla}
In this last case, blabla can be either a hero or an object, but you can't know for sure... (I think you want something like the first)
Now I'll take the first one, and say you want to add a new hero to the list. Do something like:
local num_heroes = length(Big_Table.Heroes)
Big_Table.Heroes[num_heroes+1] = some_hero_name
(or you could store a global num_heroes and update it every time you add a hero instead of calling length everytime you need an update)
You could do the same for objects btw.
To subtract a hero from the table is a little more complicated, but do you need it?
____________
|
|
Warmonger
Promising
Legendary Hero
fallen artist
|
posted September 14, 2008 02:55 PM |
|
|
variousobjects = {"SeaTemple","RangersGuild","Stockpile","MagesVault","Crypt","CounterstrikeShrine","EvilDemon","TapanisTomb"};
shrines = {"HowlShrine","CounterstrikeShrine","AvatarShrine"};
transmuters ={"SwiftHand","AltarOfFury","Breeder","LizardHide","HydraPond","WitchRock","DragonCity"};
--objects = {variousobjects, shrines, transmuters};
for i = 1, length (variousobjects) do
SetObjectEnabled(variousobjects[ i ], nil);
SetDisabledObjectMode(variousobjects[ i ], DISABLED_INTERACT);
end;
for i = 1, length (shrines) do
SetObjectEnabled(shrines[ i ], nil);
SetDisabledObjectMode(shrines[ i ], DISABLED_INTERACT);
end;
for i = 1, 7 do
object = transmuters[ i ];
SetObjectEnabled(object [ i ], nil);
SetDisabledObjectMode(object [ i ], DISABLED_INTERACT);
end;
So, there are some objects of different types. Now, if I want to disable their default behaviour, I have to call each group seperately. But I'd like to merge them into a big group which could be dealt with one single.
Then I'll set some behaviour (or description) for only one group of specific objects.
|
|
Asheera
Honorable
Undefeatable Hero
Elite Assassin
|
posted September 14, 2008 03:05 PM |
|
|
Something like this could work:
AllObjects =
{
variousobjects = {"SeaTemple","RangersGuild","Stockpile","MagesVault","Crypt","CounterstrikeShrine","EvilDemon","TapanisTomb"},
shrines = {"HowlShrine","CounterstrikeShrine","AvatarShrine"},
transmuters = {"SwiftHand","AltarOfFury","Breeder","LizardHide","HydraPond","WitchRock","DragonCity"},
}
for i, group in AllObjects do
for j, object in group do
SetObjectEnabled(object, nil)
SetDisabledObjectMode(object, DISABLED_INTERACT)
end
end
____________
|
|
Warmonger
Promising
Legendary Hero
fallen artist
|
posted September 14, 2008 04:44 PM |
|
|
Worked! I really appreciate your help
|
|
Asheera
Honorable
Undefeatable Hero
Elite Assassin
|
posted September 14, 2008 04:49 PM |
|
|
Just in case you don't know...
The for statement can be written in the standard way (for i = 1, 3 do) OR in a special "table" way (for i, v in table do)
In here, the for statement goes through all the table and gives you back the index and element. table is the table (obviously), i is the index of the current element in the table (not used often) and v is the element of the table (the value of the ith element in the table)
____________
|
|
Warmonger
Promising
Legendary Hero
fallen artist
|
posted October 22, 2008 10:03 PM |
|
|
This time I try to create a script which starts combat with random group of dragons with specified power rating, however muust have totaly messed up the syntax. It returns compilation error as soon as I try to use in operator so I can hardly test it.
function dragoncitycombat ()
local Dragons =
{
[1] = {CREATURE_DEEP_DRAGON,CREATURE_RED_DRAGON,CREATURE_BLACK_DRAGON},
[2] = {CREATURE_FIRE_DRAGON,CREATURE_MAGMA_DRAGON,CREATURE_LAVA_DRAGON,291},
[3] = {CREATURE_GREEN_DRAGON,CREATURE_GOLD_DRAGON,CREATURE_RAINBOW_DRAGON,385},
[4] = {CREATURE_BONE_DRAGON,CREATURE_SHADOW_DRAGON,CREATURE_HORROR_DRAGON,283}
};
local Dragonspower =
{
[1] = {5234,6389,6443},
[2] = {4883,6100,6070,31112},
[3] = {4942,6028,5905,19804},
[4] = {3174,3905,3872,10503}
};
groupid = (group in Dragons);
groupid = random (gropuid) + 1;
slots = group in groupid;
power = 100*day*day*factor;
power = power / slots;
local creature1, creature2, creature3, creature4 = 0,0,0,0;
local count1, cont2, count3, count4 = 0,0,0,0;
for i, object in group do
creature..i = Dragons.[group].[i_];
count..i = power / (Dragonspower.[group].[i_]);
end;
StartCombat (currenthero,nil,creature1,count1,creature2,count2,creature3,count3,creature4,count4,nil,nil,nil,nil};
end;
Yep, I'm still wokring on my NCF map and it's almost finished, apart from some additional features like this one.
|
|
Asheera
Honorable
Undefeatable Hero
Elite Assassin
|
posted October 22, 2008 10:19 PM |
|
Edited by Asheera at 23:31, 22 Oct 2008.
|
First of all I don't think this line works:
creature..i = Dragons.[group].[i_];
I don't think you can do access a variable using the ".." operator.
There's also a typo somewhere. (groupid = random (gropuid) + 1
And I don't know what you want to do here:
groupid = (group in Dragons);
The "in" thing works only in for statements afaik.
And what are these numbers (bolded):
[2] = {CREATURE_FIRE_DRAGON,CREATURE_MAGMA_DRAGON,CREATURE_LAVA_DRAGON,291},
[3] = {CREATURE_GREEN_DRAGON,CREATURE_GOLD_DRAGON,CREATURE_RAINBOW_DRAGON,385},
[4] = {CREATURE_BONE_DRAGON,CREATURE_SHADOW_DRAGON,CREATURE_HORROR_DRAGON,283}
I'll fix this script once I understand what it should do
Just tell me what you want to accomplish. To start a fight with a group of dragons based on the Dragons table? But then what's with those numbers at the end?
EDIT:
Ok I think I got it. Those numbers are NCF creatures, hmm?
Here's a fixed script (didn't test it so I'm not 100% sure it works - but I can't spot any errors in it...)
function dragoncitycombat ()
local Dragons =
{
[1] = {CREATURE_DEEP_DRAGON,CREATURE_RED_DRAGON,CREATURE_BLACK_DRAGON},
[2] = {CREATURE_FIRE_DRAGON,CREATURE_MAGMA_DRAGON,CREATURE_LAVA_DRAGON,291},
[3] = {CREATURE_GREEN_DRAGON,CREATURE_GOLD_DRAGON,CREATURE_RAINBOW_DRAGON,385},
[4] = {CREATURE_BONE_DRAGON,CREATURE_SHADOW_DRAGON,CREATURE_HORROR_DRAGON,283}
}
local Dragonspower =
{
[1] = {5234,6389,6443},
[2] = {4883,6100,6070,31112},
[3] = {4942,6028,5905,19804},
[4] = {3174,3905,3872,10503}
}
local group = random(4) + 1
local slots = length(Dragons[group])
local power = (100*day*day*factor) / slots
local creature = {nil, nil, nil, nil}
local count = {nil, nil, nil, nil}
for i = 1, slots do
creature[i] = Dragons[group][i]
count[i] = ceil(power / (Dragonspower[group][i]))
end
StartCombat(currenthero,nil,slots,creature[1],count[1],creature[2],count[2],creature[3],count[3],creature[4],count[4])
end
I used a trick in the StartCombat function. Also, you missed the third parameter, which is the number of stacks (slots variable in our case)
The trick is this: As you can see, I didn't write the four nil values at the end, because they're nil by default. However, when there are only three stacks (group 1 case), it will access creature[4] and count[4] which are NOT written in that for loop in this case. However, they are initiated with nil, so they will replace two of those last four 'default-nil-values'. The result is that it works in all cases, either with 3 or 4 stacks (it could work with 2 as well, since there are FOUR nil values at the end, but it will NOT work with 1 stack)
____________
|
|
Warmonger
Promising
Legendary Hero
fallen artist
|
posted October 27, 2008 08:35 AM |
|
|
Works! That's absolutely awesome, will have to play with it more
Thank you Asheera!
|
|
|
|