Random Mixed Neutrals on any map

Maps and the art of mapmaking.
User avatar
Asheera
Round Table Knight
Round Table Knight
Posts: 4506
Joined: 06 Jul 2008
Location: The Shadows
Contact:

Random Mixed Neutrals on any map

Unread postby Asheera » 21 Aug 2008, 13:28

Hello all, in this thread I'll give you (the map makers especially) the possibility to place random mixed neutrals on any map. I'll use the script for this.

First, add this piece of code to "scripts/common.lua" file (my creation):

Code: Select all

-- Function for handling the generation of neutrals
function CreateCreeps(creeps, creep_groups)
  local additional_stacks = {}
  local x = 1

  -- go through all creeps
  for i, creep in creeps do
    -- get current creep properties
    local creep_name = "Creep_" .. i
    local groups = creep_groups[creep.group]
    local group = groups[random(length(groups))+1]

    -- create the monster on the map with the first stack
    local stack = group[1]
    CreateMonster(creep_name, stack.type, stack.min+random(stack.max-stack.min+1), creep.x, creep.y, creep.u or 0, creep.mood or 1, creep.courage or 2, creep.rot)

    -- the additional stacks need to be created later, because it seems the engine doesn't create the creeps immediately when CreateMonster is called
    -- this means that an object with the name 'creep_name' doesn't exist yet, and AddObjectCreatures will thus fail
    -- so for now we just store the additional stack information in our temporary array
    local numStacks = length(group)
    for j = 2, numStacks do
      stack = group[j]
      additional_stacks[x] = {Name = creep_name, Id = stack.type, Num = stack.min+random(stack.max-stack.min+1)}
      x = x + 1
    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.5
  elseif x == 2 then y = 1.12
  elseif x == 3 then y = 1.4
  end

  for i, v in additional_stacks do
    AddObjectCreatures(v.Name, v.Id, v.Num*y)
  end
end
Now for each map you want random mixed neutrals, you'll need to place a map script in it. And also, don't place any neutrals on the map, the script will handle that for you.

The Map script should look like the following (this is only an example - the possibilities are endless)

Code: Select all

-- define neutrals
NeutralGroups =
{
 -- group 1
 [1] =
 {
   [1] =
   {
     [1] = {min = 50, max = 60, type =   1}, -- Peasants
   },
   [2] =
   {
     [1] = {min = 20, max = 30, type =   2}, -- Conscripts
     [2] = {min = 15, max = 35, type = 106}, -- Brutes
   },
 },

 -- group 2
 [2] =
 {
   [1] =
   {
     [1] = {min =  7, max = 11, type =   3}, -- Archers
     [2] = {min =  5, max =  8, type =   5}, -- Footmen
     [3] = {min =  1, max =  2, type = 108}, -- Vindicators
   },
   [2] =
   {
     [1] = {min =  5, max =  7, type = 108}, -- Vindicators
     [2] = {min = 13, max = 19, type = 106}, -- Brutes
   },
   [3] =
   {
     [1] = {min = 14, max = 20, type =  43}, -- Pixies
   },
   [4] =
   {
     [1] = {min =  7, max = 11, type = 146}, -- Wind Dancers
   },
   [5] =
   {
     [1] = {min =  7, max =  9, type =  93}, -- Shield Guards
     [2] = {min =  2, max =  4, type = 167}, -- Harpooners
   },
   [6] =
   {
     [1] = {min = 16, max = 24, type =  57}, -- Gremlins
     [2] = {min =  6, max =  9, type = 159}, -- Gremlin Saboteurs
   },
   [7] =
   {
     [1] = {min = 10, max = 15, type =  71}, -- Scouts
   },
   [8] =
   {
     [1] = {min =  3, max =  6, type =  73}, -- Blood Maidens
     [2] = {min =  3, max =  3, type = 139}, -- Blood Sisters
   },
   [9] =
   {
     [1] = {min = 12, max = 17, type =  16}, -- Familiars
   },
   [10] =
   {
     [1] = {min = 17, max = 27, type =  29}, -- Skeletons
     [2] = {min =  9, max = 12, type = 152}, -- Skeleton Warriors
   },
 },

 -- group 3
 [3] =
 {
   [1] =
   {
     [1] = {min = 30, max = 50, type =  3}, -- Archers
   },
   [2] =
   {
     [1] = {min =  6, max = 20, type = 71}, -- Scouts
   },
 },

 -- group 4
 [4] =
 {
   [1] =
   {
     [1] = {min = 30, max = 50, type = 4}, -- Marksmen
   },
 },

 -- group 5
 [5] =
 {
   [1] =
   {
     [1] = {min = 20, max = 40, type = 5}, -- Footmen
   },
 },

 -- group 6
 [6] =
 {
   [1] =
   {
     [1] = {min = 10, max = 15, type = 7}, -- Griffins
   },
 },

 -- group 7
 [7] =
 {
   [1] =
   {
     [1] = {min = 10, max = 15, type = 8}, -- Imperial Griffins
   },
 },

 -- group 8
 [8] =
 {
   [1] =
   {
     [1] = {min = 6, max = 9, type = 10}, -- Inquisitors
   },
 },

 -- group 9
 [9] =
 {
   [1] =
   {
     [1] = {min = 4, max = 6, type = 11}, -- Cavaliers
   },
 },

 -- group 10
 [10] =
 {
   [1] =
   {
     [1] = {min = 4, max = 6, type = 111}, -- Champions
   },
 },

 -- group 11
 [11] =
 {
   [1] =
   {
     [1] = {min = 1, max = 3, type = 14}, -- ArchAngels
   },
 },
}
MapNeutrals =
{
 [1] = {group =  1, x =  5, y = 76, rot =  90},
 [2] = {group =  2, x = 17, y = 83, rot =   0},
 [3] = {group =  3, x = 26, y = 91, rot =   0},
 [4] = {group =  4, x = 76, y = 90, rot =  90, u=1},
 [5] = {group =  1, x = 80, y = 99, rot =   0},
 [6] = {group =  1, x = 55, y =  1, rot = 270, u=1},
 [7] = {group =  3, x = 32, y = 44, rot =   0},
 [8] = {group =  8, x = 11, y = 37, rot = 180},
 [9] = {group =  5, x =  4, y = 82, rot =  90, u=1},
}

-- place neutrals on the map
CreateCreeps(MapNeutrals, NeutralGroups)
The MapNeutrals table holds the neutral "objects" on the map. You have to specify the position (x, y), if it is underground (u=1 means it's underground) and the group of the neutrals. The group is a look up in the NeutralGroups table. For example, we see that group 1 spawns two possible neutrals (chosen randomly with equal chance at the start of the map):

1. 50-60 Peasants -> not mixed with anything
2. 20-30 Conscripts AND 15-35 Brutes -> mixed neutral stack

The same for the other groups (group 2 is an interesting one, it has a lot of possible "choices" at the start of the map to choose from - some mixed, some not)

(yes, type is the creature ID)



NOTE: it takes some time to generate the neutrals at the start of the map (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 :)

I hope Map Makers will like to create maps with mixed neutrals from now on :-D (no more Garrisons mixed neutrals only, etc)
No matter how powerful one becomes, there is always someone stronger. That's why I'm in a constant pursuit of power, so I can be prepared when an enemy tries to take advantage of me.

User avatar
Asheera
Round Table Knight
Round Table Knight
Posts: 4506
Joined: 06 Jul 2008
Location: The Shadows
Contact:

Unread postby Asheera » 21 Aug 2008, 14:26

Misunderstandings? Post them here and I'll clarify ;)
No matter how powerful one becomes, there is always someone stronger. That's why I'm in a constant pursuit of power, so I can be prepared when an enemy tries to take advantage of me.

User avatar
Asheera
Round Table Knight
Round Table Knight
Posts: 4506
Joined: 06 Jul 2008
Location: The Shadows
Contact:

Unread postby Asheera » 21 Aug 2008, 19:28

Ok some clarifications:

Firstly, it will be pretty hard to re-create the neutrals on an already neutral populated map, because you'll have to make the neutrals from scratch.

Otherwise, on a new "clean" map, you don't have to do really much work, since you don't have to understand the function (just copy-paste it)



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)

Code: Select all

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 the other 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.

The NeutralGroups table contains, on the first layer, the groups.

Code: Select all

NeutralGroups =
{
  -- group 1
  [1] =
  {
  },
}
A group contains a list of possible neutral monsters. The function which I provided chooses from this list randomly.

Code: Select all

NeutralGroups =
{
  -- group 1
  [1] =
  {
    [1] =
    {
    },
    [2] =
    {
    },
  },
}
Now, a neutral monster contains a list of neutral stacks. And each stack contains a minimum value, a maximum value, and a creature type (the ID). The function I provided chooses a random value between the min and max when creating the neutral stack.

Code: Select all

NeutralGroups =
{
  -- group 1
  [1] =
  {
    [1] =
    {
     [1] = {min = 50, max = 60, type =   1}, -- Peasants
    },
    [2] =
    {
      [1] = {min = 20, max = 30, type =   2}, -- Conscripts
      [2] = {min = 15, max = 35, type = 106}, -- Brutes
    },
  },
}
In the above example, group 1 neutrals will spawn either 50-60 Peasants (not mixed stack), or 20-30 Conscripts + 15-35 Brutes (mixed stack)



Let's consider that you add another neutral with the same group on the map in underground. It should look like this:

Code: Select all

MapNeutrals 
{
  [1] = {group =  1, x =  5, y = 76, rot =  90},
  [2] = {group =  1, x = 54, y = 46, rot = 180, u = 1},
}


You can simulate the Nival neutrals with my system as well. It has 7 groups (for each tier), each with 8 sub-groups (for each race), each with different min/max values depending on creature growth, and different types depending on the creature ID of course.
No matter how powerful one becomes, there is always someone stronger. That's why I'm in a constant pursuit of power, so I can be prepared when an enemy tries to take advantage of me.

User avatar
rdeford
Assassin
Assassin
Posts: 299
Joined: 17 Apr 2007
Location: Sequim, USA
Contact:

Unread postby rdeford » 22 Aug 2008, 16:05

@ Asheera-- Nice work! It is efforts such as this that keep our HOMM community alive and well. THANKS.
rdeford, Mage Of Soquim

“Forgiving and being forgiven, loving and being loved,
living and letting live, is the simple basis for it all."

Ernest Holmes 1984

User avatar
Asheera
Round Table Knight
Round Table Knight
Posts: 4506
Joined: 06 Jul 2008
Location: The Shadows
Contact:

Unread postby Asheera » 26 Aug 2008, 11:19

I'm thinking of creating an "auto" function as well, which will generate neutrals automatically (but also much random - use the first function if you want more "customizable")

The first parameter will still be a table like MapNeutrals with the format:

Code: Select all

MapNeutrals =
{
  [1] = {group =  1, x =  5, y = 76, rot =  90},
  [2] = {group =  1, x = 76, y = 90, rot =   0, u=1},
}
However, the other table will be different. It will look something like:

Code: Select all

NeutralGroups =
{
  [1] =
  {
    MinPower = 8000,
    MaxPower = 12000,

    CreatureIDs =
    {
      [1] = {1, 2, 3},
      [2] = {4, 5, 6},
    }
  },
}
In the first layer we find the groups, like in the other one (based on tiers, whatever you like)

A group has the following properties:
MinPower -> the minimum power this neutral will have
MaxPower -> the maximum power this neutral will have

The function will then choose a random power between these two values and then generate the creatures randomly based on this.

Then we have the CreatureIDs sub-table which contains some lists of creature IDs. The function will pick one of these groups and then generate neutrals in different number of stacks (mixed) with creatures chosen randomly from that group. This is to prevent, for example, Archers mixed with Demons (not very likely - but you can still do it if you want) or 1 Dragon on your Sawmill, etc... (basically, you will limit the random selection of creatures with this)

For example, you may have two lists, one with all "good" creature IDs and the other with all "evil" creature IDs.


NOTE: I'm very busy these days, so I won't be able to do this function soon. :(
No matter how powerful one becomes, there is always someone stronger. That's why I'm in a constant pursuit of power, so I can be prepared when an enemy tries to take advantage of me.

User avatar
Asheera
Round Table Knight
Round Table Knight
Posts: 4506
Joined: 06 Jul 2008
Location: The Shadows
Contact:

Unread postby Asheera » 26 Aug 2008, 21:24

Ok, I have finished the function :)

Copy & Paste the following function at the start of the map script:

Code: Select all

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 three stacks maximum for now)


How to use the function
First of all, 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. I already explained how to do this in this thread. The table should look something like this:

Code: Select all

MapNeutrals =
{
 [1] = {group =  1, x =  5, y = 76, rot =  90},
 [2] = {group =  1, x = 76, y = 90, rot =   0, u=1},
}
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)

Code: Select all

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:

Code: Select all

 [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:

Code: Select all

CreateCreepsAuto(MapNeutrals, NeutralGroups)
No matter how powerful one becomes, there is always someone stronger. That's why I'm in a constant pursuit of power, so I can be prepared when an enemy tries to take advantage of me.

User avatar
Asheera
Round Table Knight
Round Table Knight
Posts: 4506
Joined: 06 Jul 2008
Location: The Shadows
Contact:

Unread postby Asheera » 27 Aug 2008, 09:58

IMPORTANT
I updated the function and fixed a bug. Please re-copy and paste it if you have already done so before.
No matter how powerful one becomes, there is always someone stronger. That's why I'm in a constant pursuit of power, so I can be prepared when an enemy tries to take advantage of me.


Return to “Mapmaking Guild”

Who is online

Users browsing this forum: Ahrefs [Bot] and 0 guests