RebirthDev

Mushroom Game Development

You are not logged in.

#1 2020-04-05 17:12:59

MinimumDelta
Member
Registered: 2020-02-16
Posts: 5

[Release] Custom Spawn Generation

Hey guys,

Here's something I coded for my server a while back and figured I'd share with ya'll because it's version independent and relatively simple.
The examples and instructions are all in C# and use a very different structure than Odin does, however I'm releasing an idea and a roadmap to implement the idea, not a finished product.

Let's begin.

The idea is pretty simple:
- Supplement existing spawn points with custom ones
- Use WZ foothold data to generate the new spawn points

Criteria for custom spawns being active:
- Map FieldType is 0
- Map does not have any boss mobs in it
- Map does not have any flying mobs in it

The map mob loading function:

public override void Load(int mapId)
{
	var aMobInfo = new Dictionary<int, int>(); // mobId, mobCount

	var life = Field.Template.Life.Where(l => l.Type.EqualsIgnoreCase("m")).ToList(); // filter life by mobs

	if (life.Count <= 0) return; // map has no normal spawns

	var bDoCustomSpawns = true;

	foreach (var mob in life) // populate the spawns from wz map data
	{
		var entry = new CMob(mob.TemplateID)
		{
			SpawnCY = mob.CY,
			SpawnFH = mob.Foothold,
			SpawnX = mob.X,
			SpawnIndex = Spawns.Count
		};
		Spawns.Add(entry);

		if (entry.MobTemplate.Boss || entry.MobTemplate.FlySpeed != 0) // fly speed is negative or positive
		{
			bDoCustomSpawns = false; // we dont break here because we want normal spawns to fill in
		}
	}

        // fieldtype 0 means its not a special map (IE boss, partyquest, whatever, theres a lot of types)
        // fieldtype can be found in wz map data 
	if (bDoCustomSpawns && Constants.CustomSpawn_Enabled && Field.Template.FieldType == 0)
	{
	        foreach (var mob in life) // map mob number and amount of each mob
		{
			if (aMobInfo.ContainsKey(mob.TemplateID))
			{
				aMobInfo[mob.TemplateID] += 1;
			}
			else
			{
				aMobInfo.Add(mob.TemplateID, 1);
			}
		}

		GenerateCustomSpawns(aMobInfo);
	}
}

The custom spawn generator function.

private void GenerateCustomSpawns(Dictionary<int, int> aMobInfo)
{
	var nBaseMobCount = Spawns.Count;
	var nMapFHCombinedLength = 0;

        // only non-wall footholds are used. a wall is a foothold where X1 == X2
        var footholds = Field.Footholds.NonWallFHs.Where(fh => fh.Slope <= Constants.CustomSpawn_MaxSlope); // i use a max slop of 0.45f

	foreach (var fh in footholds)
	{
		nMapFHCombinedLength += fh.Length; // calculate total map length (including platforms)
	}

        // use magic numbers to determine the map number of mobs allowed on a map
        // key metric here is the length of the map divided by the constant 125
        // the mob count fraction is the one to change to increase/decrease custom spawn count
	var nTotalMobCount = (nMapFHCombinedLength / 125) - (nBaseMobCount  * 0.65);

	while (Spawns.Count < nTotalMobCount)
	{
		foreach (var fh in footholds)
		{
			var randMob = aMobInfo.Random();

			// use weighted average odds 
			var weightedAvg = (double)randMob.Value / nBaseMobCount;

			// my odds limit is 0.05 so that the whole foothold collection gets looped multiple times
                        // if this isnt done, the custom spawn points will all be in the first couple footholds
			var spawnOdds = weightedAvg * Constants.CustomerSpawn_OddsLimit;

			if (Constants.Rand.NextDouble() > spawnOdds) continue;

			var mob = new CMob(randMob.Key) // create custom spawn point
			{
				SpawnCY = (short)((fh.Y1 + fh.Y2) / 2),
				SpawnX = (short)((fh.X1 + fh.X2) / 2),
				SpawnFH = fh.Id,
				SpawnIndex = Spawns.Count
			};

			Spawns.Add(mob); // add custom spawn to pool

                        // make the exact mob count vary by a lil
			if (Spawns.Count > nTotalMobCount && Constants.Rand.Next(100) < 25)
				return;
		}
	}
}

The foothold Slope() function:

public double Slope
            => Flat ? 0 // the foothold is flat when Y1 == Y2, we check this first to avoid having to do the expensive calculation
            : MathHelper.GetSlope(
                new double[] { X1, X2 },
                new double[] { Y1, Y2 }
                );

The MathHelper.GetSlope() function:

// https://en.wikipedia.org/wiki/Linear_regression
// https://en.wikipedia.org/wiki/Ordinary_least_squares
        public static double GetSlope(double[] xArray, double[] yArray)
        {
            if (xArray == null)
                throw new ArgumentNullException("xArray");
            if (yArray == null)
                throw new ArgumentNullException("yArray");
            if (xArray.Length != yArray.Length)
                throw new ArgumentException("Array Length Mismatch");
            if (xArray.Length < 2)
                throw new ArgumentException("Arrays too short.");

            double n = xArray.Length;
            double sumxy = 0, sumx = 0, sumy = 0, sumx2 = 0;
            for (int i = 0; i < xArray.Length; i++)
            {
                sumxy += xArray[i] * yArray[i];
                sumx += xArray[i];
                sumy += yArray[i];
                sumx2 += xArray[i] * xArray[i];
            }
            return ((sumxy - sumx * sumy / n) / (sumx2 - sumx * sumx / n));
        }

        public static double SlopeDistance(int x1, int y1, int x2, int y2)
            => Math.Sqrt(Math.Pow((x2 - x1), 2) + Math.Pow((y2 - y1), 2));

Alright so if that's not clear, please let me know.
I haven't released code like this before, and I'd like for it to be easily readable, understandable, and digestible.

Stay safe out there.

Last edited by MinimumDelta (2020-04-05 17:46:37)

Offline

Board footer

Powered by FluxBB