Author Topic: Asteroid collisions  (Read 24673 times)

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Asteroid collisions
« on: December 15, 2010, 09:47:12 PM »
Hello,

I'm working on a new level.

It has gravity and Infected AI.
My problem is that I want the asteroids to bounce off each other, instead of passing straight through one another.


This problem is twofold:

1) I need a collision engine to detect when asteroids have collided, and which two asteroids were involved in the collision.
2) Then I need to decide how the asteroids will bounce.  To do that, I need to modify the MomentumX and MomentumY values for those asteroids in a way that is appropriate to the force, relative size, and direction that each roid is travelling in, as well as the angle at which they strike one another.



I am pleased to say that I have part 1 totally sorted.
I use pythagoras to work out the distance between the centres of both asteroids.  Then, if that distance is less than Asteroid 1 Radius + Asteroid 2 Radius, they have collided.
This code works fine.


For part 2, I am struggling.
I am having real difficulty figuring out the formal rules of collision here, and how I express what happens when say, 2 billiard balls collide, in LUA.
I've no clue what sort of equations I should be modelling, or any of the maths that is involved.


Here's a diagram showing the limitations of my current understanding.


For my examples, I assume all objects are of the same size, density, and elasticity/bounciness - though I will also need to gain an understanding of the role these parameters play.



Help with this would be very much appreciated.
« Last Edit: December 15, 2010, 10:08:04 PM by annikk.exe »

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #1 on: December 15, 2010, 11:04:52 PM »
I found some useful equations here.  Hopefully they will work good...

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #2 on: December 16, 2010, 12:04:18 AM »
My notes:

Code: [Select]
1. Get a normal vector for the 2 colliding roids.

nvectorX = CoordX[1] - CoordX[2]
nvectorY = CoordY[1] - CoordY[2]


2. Next we need a unit vector (or "normalised" vector).
That's a vector with a length of 1.
To find this, we divide each vector component by the length of the vector.

So:
unitvectorX = normal vector X / length of normal vector
unitvectorY = normal vector Y / length of normal vector

uvectorX = nvectorX / math.sqrt((CoordX[1] * CoordX[1]) + (CoordY[2] * CoordY[2]))
uvectorY = nvectorY / math.sqrt((CoordX[1] * CoordX[1]) + (CoordY[2] * CoordY[2]))

3.  Now we have the Unit Vector, we need the Unit Tangent Vector.

utanvectorX = nvectorY * -1
utanvectorY = nvectorX


4.  Now we require the speed that each asteroid is travelling.
To do this we use Pythagoras triangular shenanigans on the MomentumX and MomentumY values.

speed of first asteroid = math.sqrt((MomentumX[1] * MomentumX[1]) + (MomentumY[1] * MomentumY[1]))
speed of secnd asteroid = math.sqrt((MomentumX[2] * MomentumX[2]) + (MomentumY[2] * MomentumY[2]))


5. Now we need a dot product.
To find the dot product of these two vectors:
(10,-5) (4,7)

We would use this calculation:

(10 * 4) + (-5 * 7)

= 40 + (-35)
= 5
This result is a SCALAR because it expresses an amount only, and not a direction!

The calculations we need are:

vfirstroidnormal = (unitvectorX * speed of first asteroid) + (unitvectorY * speed of first asteroid)
vfirstroidtan    = (utanvectorX * speed of first asteroid) + (utanvectorY * speed of first asteroid)
vsecndroidnormal = (unitvectorX * speed of first asteroid) + (unitvectorY * speed of first asteroid)
vsecndroidtan    = (utanvectorX * speed of first asteroid) + (utanvectorY * speed of first asteroid)


6.  Now we can find the new velocities (as scalars - only speed, no direction specified yet)

bouncedvelocity1 = vfirstroidnormal * (roid 1 mass - roid 2 mass) + (2 * roid 2 mass * vsecndroidnormal) / (roid 1 mass + roid 2 mass)
bouncedvelocity2 = vsecndroidnormal * (roid 2 mass - roid 1 mass) + (2 * roid 1 mass * vfirstroidnormal) / (roid 1 mass + roid 2 mass)

(mass is found by multiplying area by density.  area = pi * radius * radius)

7. 

unfinished..

Alex

  • Administrator
  • Ent
  • *****
  • Thank You
  • -Given: 3
  • -Receive: 14
  • Posts: 1,035

Pilchard123

  • Tester
  • Old Oak
  • ****
  • Thank You
  • -Given: 4
  • -Receive: 24
  • Posts: 932
  • Eufloria: Yes
Re: Asteroid collisions
« Reply #4 on: December 16, 2010, 03:24:52 AM »
AAAAAGGGGGGHHHHHHHH!!!!!!!!!! Mass of a spherical object should be volume*density!  (4*pi*radius*radius*radius)/3

Ok, screeching done. Is this your big bang map? Momentum really gets fun when things join together.

IIRC, it's possible to just deal with the X and Y values for momentum separately, which may make your map easier.




'Kay,

Case 1 - dealt with.
Case 2 - momentum is always conserved, so the total momentum after the collision is 25 + (-75) = -50, so 50 kgm/s to the left.
Case 3 - X : 25 + (-75) = -50, so 50 kgm/s to the left.
            Y : (-25) + 75 = 50, so 50 kgm/s to the to.
Case 4 - X : 25 + (-75) = -50, so 50 kgm/s to the left.
            Y : 0 + 0 = 0, so as the two bodies have equal masses they must travel vertically at equal and opposite velocities.
« Last Edit: December 16, 2010, 04:45:19 AM by Pilchard123 »

Sniped50

  • Sapling
  • **
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 97
Re: Asteroid collisions
« Reply #5 on: December 17, 2010, 05:24:07 AM »
@Pilchard123
Heh heh heh...   Eufloria doesn't use 3D asteroids you know. You're thinking of SPHERES. Eufloria only uses 2D CIRCLES, so annikk got it right the first time. Sorry to rain on your parade, though.

@Annikk.exe
Quick question, are these collisions going to be elastic (i.e. no kinetic energy is lost)? It would not only help to keep the map moving, but it would also stop the asteroids from clumping together in the centre because they lost most of their momentum AND because of gravity.
Just thought I'd mention it. It would be embarassing if you released a beta version of the map and THAT came up! :o

Eufloria Admin

  • Global Moderator
  • Seedling
  • *****
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 38
Re: Asteroid collisions
« Reply #6 on: December 18, 2010, 01:24:05 AM »
It's 2D so the mass is equal to the area * density

Case 4.  Think of two pool balls striking each other in that way, glancing off each other.  Does your conclusion seem logical?  It does not, to me...  if they strike at an angle, they go bouncing off at an angle.


Also yes these are elastic collisions.

Pilchard123

  • Tester
  • Old Oak
  • ****
  • Thank You
  • -Given: 4
  • -Receive: 24
  • Posts: 932
  • Eufloria: Yes
Re: Asteroid collisions
« Reply #7 on: December 18, 2010, 01:47:15 AM »
It is logical: the vertical components of the velocities will be equal, so the vertical speeds will be the same. Horizontally, the speeds could be totally different.

More stuff - it is possible to work it all out. Momentum is mass*velocity, kinetic (movement) energy is (mass*velocity*velocity)/2. Both must be conserved.

http://en.wikipedia.org/wiki/Elastic_collision#Two-_and_three-dimensional
« Last Edit: December 18, 2010, 01:57:18 AM by Pilchard123 »

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #8 on: December 18, 2010, 11:16:38 AM »
Maybe it's not so clear from the diagram.  Case 4 differs from the others in that the balls don't strike head on.  Instead, they glance off each other by an unspecified amount.

Have you ever played pool?  Or snooker or something?  If a ball strikes another, but it doesn't hit it dead on, it causes the struck ball to spin off at an angle - and not the same angle as the striking ball was travelling in.  I don't know what the angle is, or what maths governs it..  but it's definitely an angle.

In the diagram in Case 4, after the collision I would expect the ball on the left to end up travelling north-west.  The ball on the right would be travelling south-west, I think.
The fact that they strike each other at an angle - and that they are both round - causes them to have vertical as well as horizontal components to their momentum following the collision.  I am having trouble trying to figure out the maths that governs these angles.

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #9 on: December 18, 2010, 11:29:38 AM »
This diagram from the wikipedia article you linked shows exactly what I mean :>




It also kind of shows me how to work it out, I think...  The article gives the equations for if the second body is at rest, but I think I can see how I would combine it with another momentum vector.. :>


I just got back from an epic journey, though.  Sleep time now.  :>

Pilchard123

  • Tester
  • Old Oak
  • ****
  • Thank You
  • -Given: 4
  • -Receive: 24
  • Posts: 932
  • Eufloria: Yes
Re: Asteroid collisions
« Reply #10 on: December 19, 2010, 09:00:24 PM »
Ooh - possible problem.

Your MomentumX and MomentumY - are they the velocities of the roids, or are they really the momentum? Could have a bearing on how you code, etc.

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #11 on: December 19, 2010, 10:59:20 PM »
There are three parameters that pertain to movement: Coord, Momentum and Acceleration:

CoordX[1]
CoordY[1]
MomentumX[1]
MomentumY[1]
AccelerationX[1]
AccelerationY[1]


Coords describe the X and Y position of an asteroid's centre.
Momentum describes how much that asteroid will move (X and Y) in the next cycle of the gravity engine.  Each cycle, the momentum is added (or subtracted) from the Coord, so as to give the new position of the asteroid.
Acceleration describes how much ADDITIONAL momentum will be added or subtracted from the corresponding Momentum value, based on gravity (and any bouncing) during the next cycle of the gravity engine.  Acceleration is zeroed each cycle and recalculated for the new positions and corresponding gravitational influence of all asteroids.


The velocity of an asteroid would be a scalar (magnitude only) obtained by finding the Hypotenuse of the triangle with sides MomentumX and MomentumY for the Adjacent and Opposite, for any given asteroid.  Converting between them shouldn't be a problem..

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #12 on: December 19, 2010, 11:40:40 PM »
Not working :\


Code: [Select]
-- first up, what's the total mass of these colliding objects?
imass = math.pi * roidradius[i] * roidradius[i]
jmass = math.pi * roidradius[j] * roidradius[j]
totalmass = imass + jmass

-- what proportion from 0 to 1 of the mass belongs to i?

jprop = imass / jmass

-- and what proportion of the mass belongs to j?
iprop = jmass / imass



-- change the momentums of both roids for the bounce.


--Calculate the collision angle (A) by using atan2 applied to the difference of the coordinates of each object (x, y):


A = math.atan2((CoordY[i] - CoordY[j]), (CoordX[i] - CoordX[j]))



-- calculate the velocity of both roids

-- i

ivelocity = math.sqrt((MomentumX[i] * MomentumX[i]) + (MomentumY[i] * MomentumY[i]))

-- j

jvelocity = math.sqrt((MomentumX[j] * MomentumX[j]) + (MomentumY[j] * MomentumY[j]))


-- Use the collision angle (A), the ball's initial velocity (u) and ball's initial direction (D) to derive it's x/y velocity in the new rotated coordinate system:

Di = math.tan(CoordX[i] / CoordY[i])
Dj = math.tan(CoordX[j] / CoordY[j])



v1x = ivelocity * math.cos(Di - A)
v1y = ivelocity * math.sin(Di - A)
v2x = jvelocity * math.cos(Dj - A)
v2y = jvelocity * math.sin(Dj - A)


-- Now that we have the collision aligned along the x-axis, all we have to do is apply the 1D collision equation to vx. We will call the final x-velocities for each ball f1x and f2x:

f1x = (v1x * (imass - jmass) + (2 * jmass * v2x)) / totalmass
f2x = (v2x * (imass - jmass) + (2 * jmass * v1x)) / totalmass


-- Now that we we have the final x and y velocities in the rotated coordinate system, we must convert everything back to a normal Cartesian coordinate system, as follows:

v1 = math.sqrt((f1x * f1x) + (v1y * v1y))
v2 = math.sqrt((f2x * f2x) + (v2y * v2y))

D1 = math.atan2(v1y,f1x) + A
D2 = math.atan2(v2y,f2x) + A


-- Now we have the final angle and momentum as a hypotenuse... we can calculate the opposite and adjacent to obtain new momentumX and momentumY

MomentumY[i] = v1 * math.cos(D1)
MomentumX[i] = v1 * math.sin(D1)

MomentumX[j] = v2 * math.cos(D2)
MomentumY[j] = v2 * math.sin(D2)


« Last Edit: December 19, 2010, 11:48:01 PM by annikk.exe »

Widget

  • Sapling
  • **
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 97
Re: Asteroid collisions
« Reply #13 on: December 19, 2010, 11:51:34 PM »
Ack, I wish I could help but I've not used any maths worthy of the name in the last decade. It's a really exciting idea but well over my head.

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #14 on: December 20, 2010, 12:31:32 AM »
Think I've cracked it :>


Here's the code.


Code: [Select]
-- check if there has been a collision
-- if collision = true then

comboradius = roidradius[i] + roidradius[j]

if collision[i] ~= true then
collision[i] = false
end

if collision[j] ~= true then
collision[j] = false
end


if math.sqrt(((CoordX[j] - CoordX[i])^2 + (CoordY[j] - CoordY[i])^2)) < comboradius then
-- a collision has occurred!
-- modify momentum for bounce values

collision[j] = true
collision[i] = true


-- first up, what's the total mass of these colliding objects?
imass = math.pi * roidradius[i] * roidradius[i]
jmass = math.pi * roidradius[j] * roidradius[j]
totalmass = imass + jmass




-- change the momentums of both roids for the bounce.

dx = MomentumX[i] - MomentumX[j]
dy = MomentumY[i] - MomentumY[j]
collision_angle = math.atan2(dy, dx)
magnitude_1 = math.sqrt((MomentumX[i] * MomentumX[i]) + (MomentumY[i] * MomentumY[i]))
magnitude_2 = math.sqrt((MomentumX[j] * MomentumX[j]) + (MomentumY[j] * MomentumY[j]))
direction_1 = math.atan2(MomentumY[i], MomentumX[i])
direction_2 = math.atan2(MomentumY[j], MomentumX[j])
new_xspeed_1 = magnitude_1 * math.cos(direction_1 - collision_angle)
new_yspeed_1 = magnitude_1 * math.sin(direction_1 - collision_angle)
new_xspeed_2 = magnitude_2 * math.cos(direction_2 - collision_angle)
new_yspeed_2 = magnitude_2 * math.sin(direction_2 - collision_angle)
final_xspeed_1 = ((imass - jmass) * new_xspeed_1 + (jmass + jmass) * new_xspeed_2) / totalmass
final_xspeed_2 = ((imass + imass) * new_xspeed_1 + (jmass - imass) * new_xspeed_2) / totalmass
final_yspeed_1 = new_yspeed_1
final_yspeed_2 = new_yspeed_2
MomentumX[i] = math.cos(collision_angle) * final_xspeed_1 + math.cos(collision_angle + math.pi / 2) * final_yspeed_1
MomentumY[i] = math.sin(collision_angle) * final_xspeed_1 + math.sin(collision_angle + math.pi / 2) * final_yspeed_1
MomentumX[j] = math.cos(collision_angle) * final_xspeed_2 + math.cos(collision_angle + math.pi / 2) * final_yspeed_2
MomentumY[j] = math.sin(collision_angle) * final_xspeed_2 + math.sin(collision_angle + math.pi / 2) * final_yspeed_2


else
-- else if there wasn't a collision then
-- ..add the appropriate amount of acceleration as normal
AccelerationX[i] = AccelerationX[i] + (NormalisedVectorX * Fgx / ((roidradius[i] * roidradius[i]) * math.pi) * density[i])
AccelerationY[i] = AccelerationY[i] + (NormalisedVectorY * Fgy / ((roidradius[i] * roidradius[i]) * math.pi) * density[i])
xdiff = CoordX[i] - CoordX[j]
ydiff = CoordY[i] - CoordY[j]
NormalisedVectorX = xdiff / vectorlength
NormalisedVectorY = ydiff / vectorlength
AccelerationX[j] = AccelerationX[j] + (NormalisedVectorX * Fgx / ((roidradius[j] * roidradius[j]) * math.pi) * density[j])
AccelerationY[j] = AccelerationY[j] + (NormalisedVectorY * Fgy / ((roidradius[j] * roidradius[j]) * math.pi) * density[j])
end

Widget

  • Sapling
  • **
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 97
Re: Asteroid collisions
« Reply #15 on: December 20, 2010, 12:45:00 AM »
I hope that's the case, and very well done. This'll be a really exciting mechanic to play with.
I'm always amazed by the ambition of your ideas  ;)

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #16 on: December 20, 2010, 12:55:40 AM »
I'm having a problem with asteroids getting stuck inside each other.  I guess I'll figure out something to stop that from happening..

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #17 on: December 20, 2010, 02:31:05 AM »
Ok, they shake themselves free now... But I think ultimately, the reason why they are sometimes getting stuck together instead of bouncing off like they should is because the collision detection doesn't detect perfectly where they would intersect, so they are allowed to overlap... and then with the influence of gravity pulling them together, sometimes the "bounce" doesn't bounce them clear of each other...and they get stuck.  The next cycle sees them overlapping and bounces them again, the opposite way... and they sort of judder about together until eventually wriggling free.





So.

I'm currently bouncing after the asteroids have already overlapped.  What I really need to do is reverse them by just the right amount so that they are _just_ touching, but not overlapping.  I need to move them backward to those positions, then carry out the bounce from there.  :>

Wonder how I do that. :>

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #18 on: December 20, 2010, 05:10:29 AM »
Hmm, I didn't completely solve that problem... but regardless, I now have something approaching a pretty playable level.

Testing it now.  It's fun. :D

Sniped50

  • Sapling
  • **
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 97
Re: Asteroid collisions
« Reply #19 on: December 20, 2010, 06:03:30 AM »
Oooh, could we have a play around with it? ;D

For research purposes, you understand... :P

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #20 on: December 20, 2010, 06:11:25 AM »
After I add victory conditions.  And a screen wrap bounding box.  And maybe a paralax scrolling engine.

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #21 on: December 20, 2010, 09:27:37 PM »
Ok the bounding box is in.  Not sure if I'll bother with a parallax engine for this level, or leave it for the next level.  However, there is a big problem with the AI.  It stops spreading to new asteroids after a while, and just sits there.  I may need to overhaul the AI engine...

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #22 on: December 20, 2010, 11:59:53 PM »
Eh... might as well, I guess.  It's going to take me a while to re-write the Infected AI, so I guess the interested folks can take a look at this in the mean time.
This has Infected AI version 1, which is not very good on dynamic maps like this one.  So it won't be much challenge to beat it, I think.  There also aren't any victory/loss conditions in this.  Very much in development.. :P

Copy and paste this into a LUA file.
Feel free to play around with the code and change stuff or whatever, but please don't release any levels with any of the new functionalities in this, until I've made my own debut.  :>



Code: [Select]
function LevelSetup()



-- ** None of these are strictly necessary for Gravity... change as you like. **
SetBackdropColour(0,0,0)

Globals.Agents.MaxSpeed=600
Globals.Agents.MinSpeed=200

Globals.Mines.MinSpeed=1200
Globals.Mines.MaxSpeed=1200
Globals.Mines.MinHealth=2000
Globals.Mines.MaxHealth=2000

Globals.Asteroids.MaxTrees=3
Globals.Asteroids.MinRadius=125
    Globals.Asteroids.MaxRadius=725
    Globals.Asteroids.RadiusPowerRule=1.5
Globals.Asteroids.MinCoreHealth=50
Globals.Asteroids.MaxCoreHealth=900
Globals.Asteroids.CoreHealthPower=1
    Globals.Asteroids.MinSendDistance=60000
    Globals.Asteroids.MaxSendDistance=60000
    Globals.Asteroids.SendPowerRule=1.4
Globals.Asteroids.SpawnCap=40
Globals.Asteroids.SeedlingCap=1000
Globals.G.Asteroids=(0)

Globals.AI.GraceTimer=(9999999)


Globals.G.EnemyFactionsMin=(0)
Globals.G.EnemyFactionsMax=(0)
Globals.G.MinAsteroidSeparation=100
Globals.G.MaxAsteroidNeighbourDist=60000
Globals.G.GreysProbability=0

Globals.Structures.FlowerProbability=(0.1)

SetVignetteAlpha(0)
-- **





-- ** Initialise Gravity Variables.  No need to change anything here.

AccelerationX = {}
AccelerationY = {}
MomentumX = {}
MomentumY = {}
density = {}
CoordX = {}
CoordY = {}
roidradius = {}
str = {}
ene = {}
spe = {}
collision = {}
collisiontimer = {}
Timer = 0
collidedbefore = {}
oldCoordX = {}
oldCoordY = {}

-- **





-- ** G is the gravitational constant.  Affects how powerful gravity is in the entire map.  Change this as you like. **

G = 0.01

-- **






-- ****************************************
-- **********Asteroid Creation*************
-- ****************************************
--

-- How To.



-- * First we declare the asteroid ID to the Gravity Engine.
-- EG:
-- roid = 0



-- * Next comes the Acceleration array slot init.  These should always be set to 0.
-- EG:
-- AccelerationX[roid] = 0
-- AccelerationY[roid] = 0



-- * MomentumX and MomentumY declare the initial velocity of the asteroid.
-- EG:
-- MomentumX[roid] = 10
-- MomentumY[roid] = 0

-- This example would produce an asteroid that is drifting east when the game begins.



-- * Density governs how dense the asteroid is.  An asteroid that was made of metal has a higher density than an asteroid made of gas.  Higher density means stronger gravity per unit of radius.
-- EG:
-- density[roid] = 1



-- * The coordinates of the asteroid when the game begins.
-- EG:
-- CoordX[roid] = 12000
-- CoordY[roid] = -8000



-- * Finally, we set the radius of the asteroid.  Bigger asteroids have more gravity, but also weigh more so move in a "heavier" fashion.
-- EG:
-- roidradius[roid] = 400




-- Now we have declared all necessary gravity variables for the new asteroid, we can create it.
-- EG:
-- a = AddAsteroidWithAttribs(CoordX[roid],CoordY[roid],0.5,0.5,1)
-- a.Owner = 1
-- a.TreeCap = 2
-- a:SetRadius(roidradius[roid])
-- a:Reveal(1)
-- a.Moveable = False
-- any other commands you would like to run on this asteroid...



-- There are 3 different possible gravity behaviours; well-only, and full gravity, and static.
-- The asteroids MUST be created in the correct sections.
-- Please see below examples of all three different classes of asteroids.




-- ***
-- 1.  THE BELOW ASTEROIDS HAVE A GRAVITY WELL BUT DO NOT THEMSELVES MOVE
-- ***


-- Asteroid 0 - A Sun
-- gravity variables
roid = 0
AccelerationX[roid] = 0
AccelerationY[roid] = 0
MomentumX[roid] = 0
MomentumY[roid] = 0
density[roid] = 10
CoordX[roid] = 0
CoordY[roid] = 0
oldCoordX[roid] = 0
oldCoordY[roid] = 0
roidradius[roid] = 300

-- Creation
a = AddAsteroidWithAttribs(CoordX[roid],CoordY[roid],0.5,0.5,1)
a.Owner = 1
a.TreeCap = 6
a:SetRadius(roidradius[roid])
a:Reveal(1)
a.Moveable = False
a:AddSeedlings(60)
a:AddDysonTree()











-- ** counter for gravity behaviour divisions, do not remove **
wellonlythreshold = roid + 1
-- **

-- ***
-- 2.  THE BELOW ASTEROIDS HAVE A GRAVITY WELL, AND MOVE
-- ***


-- Asteroid 1 - A moving asteroid
-- gravity variables

name = 0

for setcollide = 0, 25 do
collidedbefore[setcollide] = 0
end


for makeroids = 1,25 do
roid = makeroids
AccelerationX[roid] = 0
AccelerationY[roid] = 0
CoordX[roid] = math.random(-15000,15000)
CoordY[roid] = math.random(-15000,15000)
oldCoordX[roid] = CoordX[roid]
oldCoordY[roid] = CoordY[roid]


if CoordX[roid] < 0 then

MomentumY[roid] = math.random(1,5)

else

MomentumY[roid] = math.random(-5,1)

end

if CoordY[roid] < 0 then

MomentumX[roid] = math.random(-5,1)

else

MomentumX[roid] = math.random(1,5)

end





density[roid] = 1
roidradius[roid] = math.random(110,260)
str[roid] = (math.random(1,10) / 10) * (roidradius[roid] / 260)
ene[roid] = (math.random(1,10) / 10) * (roidradius[roid] / 260)
spe[roid] = (math.random(1,10) / 10) * (roidradius[roid] / 260)

-- Creation
a = AddAsteroidWithAttribs(CoordX[roid],CoordY[roid],str[roid],ene[roid],spe[roid])
a.Owner = 2
a.TreeCap = 4
a:SetRadius(roidradius[roid])
a:Reveal(1)
a.Moveable = False
name = name + 1
a.Name = name



end

GetAsteroid(2):AddSeedlings(250, 2, 0.1, 0.1, 1)




-- ** counter for gravity behaviour divisions, do not remove **
gravroidsthreshold = roid
-- **

-- 3.  THE BELOW ASTEROIDS DO NOT MOVE AND DO NOT HAVE A GRAVITY WELL











-- spacer asteroid, used to make sure the level is big enough for asteroids to wander about on long, eliptical orbits.  Change to taste.
a = AddAsteroidWithAttribs(55000,5000,0.5,0.5,0.5)
a.Owner = 0
a.TreeCap = 4
a:SetRadius(1)
a.Moveable = False



roidnumber = roid
-- END ASTEROID CREATION


-- START AI ENGINE INITIALISATION

rcolour = 0

endfinal = false

finality = false

dangertimer = {}

purgetimer = 0




for dset = 0,roidnumber do
dangertimer[dset] = GetGameTime() - 90
end

danger = {}

torchlit = {}

constructionmetric = {}


gathermetric = {}

gatherexists = 0


gatherpoint = GetAsteroid(33)




-- END AI ENGINE INITIALISATION


timeoff = false



end

function LevelDraw()



-- left line
Line1x1 = -18000
Line1y1 = -18000
Line1x2 = -18000
Line1y2 = 18000

-- bottom line
Line2x1 = -18000
Line2y1 = -18000
Line2x2 = 18000
Line2y2 = -18000

-- right line
Line3x1 = 18000
Line3y1 = -18000
Line3x2 = 18000
Line3y2 = 18000

-- top line
Line4x1 = -18000
Line4y1 = 18000
Line4x2 = 18000
Line4y2 = 18000

DrawLine(Line1x1,Line1y1,Line1x2,Line1y2,0,0,1,1,0,1,0,1,20)
DrawLine(Line2x1,Line2y1,Line2x2,Line2y2,0,0,1,1,0,1,0,1,20)
DrawLine(Line3x1,Line3y1,Line3x2,Line3y2,0,0,1,1,0,1,0,1,20)
DrawLine(Line4x1,Line4y1,Line4x2,Line4y2,0,0,1,1,0,1,0,1,20)

end

function LevelLogic()

-- Zoom the camera
SetCameraZoom(9)



-- *** Set the send distances you want for each asteroid here.

-- *** I know you normally do it in Level Setup but in gravityland we do it here.
GetAsteroid(0).SendDistance = 6000

for ii = 1,roidnumber do
GetAsteroid(ii).SendDistance = 500 + roidradius[ii] * 10
end
-- *** End setting of send distances



while GameRunning() do

-- *** YOUR LOOPED COMMANDS GO HERE *** --


for wrap = 0,roidnumber do
if CoordX[wrap] > 18000 then
CoordX[wrap] = -18000
end

if CoordY[wrap] > 18000 then
CoordY[wrap] = -18000
end

if CoordX[wrap] < -18000 then
CoordX[wrap] = 18000
end

if CoordY[wrap] < -18000 then
CoordY[wrap] = 18000
end
end


-- Make the asteroids appear, one by one...



-- for slow = 0,roidnumber do
-- if MomentumX[slow] > 15 or MomentumX[slow] < -15 then
-- MomentumX[slow] = MomentumX[slow] * 0.98
-- end

-- if MomentumY[slow] > 15 or MomentumY[slow] < -15 then
-- MomentumY[slow] = MomentumY[slow] * 0.98
-- end

-- end


-- *** YOUR LOOPED COMMANDS END HERE *** ---








-- START GRAVITY ENGINE
-- change things below this line at your own peril!!
-- Rate Limiter - necessary to pause gravity simulation if the game is paused.
if GetGameTime() > Timer + 0.0 then
Timer = GetGameTime()
-- Get values for the the array
for i = 0, gravroidsthreshold do
for j = i + 1, gravroidsthreshold do
-- calculate Fgx and Fgy between i and j, then...
Fgx = (G * ((roidradius[i] * roidradius[i]) * math.pi * density[i]) * ((roidradius[j] * roidradius[j]) * math.pi * density[j])) / ((CoordX[j] - CoordX[i])^2 + (CoordY[j] - CoordY[i])^2)
Fgy = (G * ((roidradius[i] * roidradius[i]) * math.pi * density[i]) * ((roidradius[j] * roidradius[j]) * math.pi * density[j])) / ((CoordX[j] - CoordX[i])^2 + (CoordY[j] - CoordY[i])^2)
-- now we have the force of gravity x and y, as a scalar.  we must find the direction to point in:
xdiff = CoordX[j] - CoordX[i]
ydiff = CoordY[j] - CoordY[i]
-- find the length of the vector..
vectorlength = math.sqrt(xdiff^2 + ydiff^2)
-- divide the vectors by the length to normalise
NormalisedVectorX = xdiff / vectorlength
NormalisedVectorY = ydiff / vectorlength
-- these normalised vectors are values between 0 and 1 that give us a direction :>



-- check if there has been a collision
-- if collision = true then

comboradius = roidradius[i] + roidradius[j]

if collision[i] ~= true then
collision[i] = false
end

if collision[j] ~= true then
collision[j] = false
end


if math.sqrt(((CoordX[j] - CoordX[i])^2 + (CoordY[j] - CoordY[i])^2)) < comboradius then
-- a collision has occurred!

-- update the positions of the colliding (and overlapping) roids to the positions from the previous cycle (lazy method)



-- CoordX[i] = oldCoordX[i]
-- CoordY[i] = oldCoordY[i]
-- CoordX[j] = oldCoordX[j]
-- CoordY[j] = oldCoordY[j]



-- modify momentum for bounce values

collidedbefore[i] = collidedbefore[i] + 1
collidedbefore[j] = collidedbefore[j] + 1

collision[j] = true
collision[i] = true


-- first up, what's the total mass of these colliding objects?
imass = math.pi * roidradius[i] * roidradius[i]
jmass = math.pi * roidradius[j] * roidradius[j]
totalmass = imass + jmass




-- change the momentums of both roids for the bounce.
if collidedbefore[i] < 2 and collidedbefore[j] < 2 then
-- These asteroids are not stuck together - bounce them.


dx = MomentumX[i] - MomentumX[j]
dy = MomentumY[i] - MomentumY[j]
collision_angle = math.atan2(dx, dy)
magnitude_1 = math.sqrt((MomentumX[i] * MomentumX[i]) + (MomentumY[i] * MomentumY[i]))
magnitude_2 = math.sqrt((MomentumX[j] * MomentumX[j]) + (MomentumY[j] * MomentumY[j]))
direction_1 = math.atan2(MomentumY[i], MomentumX[i])
direction_2 = math.atan2(MomentumY[j], MomentumX[j])
new_xspeed_1 = 1.05 * magnitude_1 * math.cos(direction_1 - collision_angle)
new_yspeed_1 = 1.05 * magnitude_1 * math.sin(direction_1 - collision_angle)
new_xspeed_2 = 1.05 * magnitude_2 * math.cos(direction_2 - collision_angle)
new_yspeed_2 = 1.05 * magnitude_2 * math.sin(direction_2 - collision_angle)
final_xspeed_1 = ((imass - jmass) * new_xspeed_1 + (jmass + jmass) * new_xspeed_2) / totalmass
final_xspeed_2 = ((imass + imass) * new_xspeed_1 + (jmass - imass) * new_xspeed_2) / totalmass
final_yspeed_1 = new_yspeed_1
final_yspeed_2 = new_yspeed_2
MomentumX[i] = math.cos(collision_angle) * final_xspeed_1 + math.cos(collision_angle + math.pi / 2) * final_yspeed_1
MomentumY[i] = math.sin(collision_angle) * final_xspeed_1 + math.sin(collision_angle + math.pi / 2) * final_yspeed_1
MomentumX[j] = math.cos(collision_angle) * final_xspeed_2 + math.cos(collision_angle + math.pi / 2) * final_yspeed_2
MomentumY[j] = math.sin(collision_angle) * final_xspeed_2 + math.sin(collision_angle + math.pi / 2) * final_yspeed_2







else
-- These asteroids are stuck together - don't bounce.


end

-- ..Now add the appropriate amount of acceleration
AccelerationX[i] = AccelerationX[i] + (NormalisedVectorX * Fgx / ((roidradius[i] * roidradius[i]) * math.pi) * density[i])
AccelerationY[i] = AccelerationY[i] + (NormalisedVectorY * Fgy / ((roidradius[i] * roidradius[i]) * math.pi) * density[i])
xdiff = CoordX[i] - CoordX[j]
ydiff = CoordY[i] - CoordY[j]
NormalisedVectorX = xdiff / vectorlength
NormalisedVectorY = ydiff / vectorlength
AccelerationX[j] = AccelerationX[j] + (NormalisedVectorX * Fgx / ((roidradius[j] * roidradius[j]) * math.pi) * density[j])
AccelerationY[j] = AccelerationY[j] + (NormalisedVectorY * Fgy / ((roidradius[j] * roidradius[j]) * math.pi) * density[j])






else


collidedbefore[i] = 0
collidedbefore[j] = 0

-- else if there wasn't a collision then
-- ..add the appropriate amount of acceleration as normal
AccelerationX[i] = AccelerationX[i] + (NormalisedVectorX * Fgx / ((roidradius[i] * roidradius[i]) * math.pi) * density[i])
AccelerationY[i] = AccelerationY[i] + (NormalisedVectorY * Fgy / ((roidradius[i] * roidradius[i]) * math.pi) * density[i])
xdiff = CoordX[i] - CoordX[j]
ydiff = CoordY[i] - CoordY[j]
NormalisedVectorX = xdiff / vectorlength
NormalisedVectorY = ydiff / vectorlength
AccelerationX[j] = AccelerationX[j] + (NormalisedVectorX * Fgx / ((roidradius[j] * roidradius[j]) * math.pi) * density[j])
AccelerationY[j] = AccelerationY[j] + (NormalisedVectorY * Fgy / ((roidradius[j] * roidradius[j]) * math.pi) * density[j])
end
end
end


for pass = wellonlythreshold,gravroidsthreshold do
MomentumX[pass] = MomentumX[pass] + AccelerationX[pass]
MomentumY[pass] = MomentumY[pass] + AccelerationY[pass]






-- national speed limit

if math.sqrt(MomentumX[pass]^2 + MomentumY[pass]^2) > 160 then
MomentumX[pass] = MomentumX[pass] * 0.93
MomentumY[pass] = MomentumY[pass] * 0.93
end

if math.sqrt(MomentumX[pass]^2 + MomentumY[pass]^2) > 60 then
MomentumX[pass] = MomentumX[pass] * 0.98
MomentumY[pass] = MomentumY[pass] * 0.98
end


if math.sqrt(MomentumX[pass]^2 + MomentumY[pass]^2) > 10 then
MomentumX[pass] = MomentumX[pass] * 0.999
MomentumY[pass] = MomentumY[pass] * 0.999
end




-- MOVE GETASTEROID(PASS) by MomentumX and MomentumY

if collision[pass] == false then
oldCoordX[pass] = CoordX[pass]
oldCoordY[pass] = CoordX[pass]
end

CoordX[pass] = CoordX[pass] + MomentumX[pass]
CoordY[pass] = CoordY[pass] + MomentumY[pass]
GetAsteroid(pass):MoveTo(CoordX[pass], CoordY[pass])
AccelerationX[pass] = 0
AccelerationY[pass] = 0
end
end
-- END GRAVITY ENGINE


-- *** START INFECTED AI ENGINE *** --



if rcolour > 0 then
rcolour = rcolour - 1
end

--SetBackdropColour(rcolour,0,0)



--AI


for check = 0,roidnumber do
checkedroid = GetAsteroid(check)





if GetGameTime() > purgetimer + 35 then
purgetimer = 0
end



-- First, find out which asteroids are close enough to travel in 1 jump, and

traversable = {}
pathsavailable = 0

attackable = {}
attackpaths = 0

actiontaken = 0

increasemetricvote = 1
increasegathermetricvote = 1
confirmedzero = 0
confirmedgatherpoint = 0




if GetEmpire(2):OwnsAsteroidID(checkedroid.ID) then

-- MINE CHECK NOT REQUIRED IN THIS MAP

--if checkedroid:GetNumSeedlings(1) == 0 and checkedroid:GetNumSeedlings(2) > 5 then
-- for minecheck = 0,roidnumber do

-- if GetEmpire(2):OwnsAsteroidID(minecheck) == true and GetAsteroid(minecheck):GetNumMines(1) > 0 and checkedroid:GetNumMines(1) == 0 then
-- checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),GetAsteroid(minecheck))
-- end

-- end

--end

for u = 0,roidnumber do

if GetAsteroid(u) ~= checkedroid then
-- only learn about this asteroid if it's not the one being checked
-- can we send seeds to this roid from the checked one?

if (GetAsteroid(checkedroid.ID):GetSendDistance() + roidradius[u]) > math.sqrt(((CoordX[u] - CoordX[checkedroid.ID])^2) + ((CoordY[u] - CoordY[checkedroid.ID])^2)) then
-- we can !  Now is this a friendly path or an attackable path?
if GetEmpire(2):OwnsAsteroidID(u) == false then


attackable[attackpaths] = GetAsteroid(u)
attackpaths = attackpaths + 1

-- moar aggression ! advantage pressing, etc
if checkedroid:GetNumSeedlings(2) > 120 and GetAsteroid(u):GetNumSeedlings(1) < (checkedroid:GetNumSeedlings(2) / 2) and checkedroid:GetNumSeedlings(1) < 10 then
checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),GetAsteroid(u))
if rcolour < 105 then
rcolour = rcolour + 150
end


end


elseif GetAI(2):OwnsAsteroidID(u) == true then

traversable[pathsavailable] = GetAsteroid(u)
pathsavailable = pathsavailable + 1
if GetAsteroid(u):GetNumTrees() < GetAsteroid(u).TreeCap then
constructionmetric[u] = 0
end

else

end

end

-- ok, this roid is not in range for us.






end
-- ok, we were trying to check ourselves.





end
-- end of neighbour-checking sequence









-- end of metric checking sequence

else

torchlit[checkedroid.ID] = nil
constructionmetric[checkedroid.ID] = nil

if checkedroid.ID ~= 4 or checkedroid.ID ~= 5 then
gathermetric[checkedroid.ID] = nil
end

end













-- we have selected "checkedroid" for checking.  We must find out all we can about the asteroid and it's surroundings, and act appropriately.
if GetAI(2):OwnsAsteroidID(checkedroid.ID) == true and checkedroid:GetNumMines(1) > 0 then

-- do buggerysquat.


elseif GetAI(2):OwnsAsteroidID(checkedroid.ID) == true then
-- this roid is ours ! :>




-- if pathsavailable == 0 then
-- Orphan Control
-- boltfriendly = GetEmpire(2):GetRandomAsteroid()
-- letsgo = 0

-- for iii = 0,attackpaths do

-- if attackable[iii] ~= nil then
-- letsgo = letsgo + (attackable[iii]:GetNumSeedlings(1))
-- end

-- end

-- if letsgo > checkedroid:GetNumSeedlings(2) and checkedroid:GetNumSeedlings(2) > 10 and checkedroid:GetNumSeedlings(2) < 39 then

-- checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),boltfriendly)


-- elseif letsgo < checkedroid:GetNumSeedlings(2) and checkedroid:GetNumSeedlings(2) > (39 + (attackable[0]:GetNumDysonTrees() * 5) + (attackable[0]:GetNumDefenseTrees() * 15)) then

-- checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),attackable[0])
-- if rcolour < 105 then
-- rcolour = rcolour + 150
-- end


-- elseif attackable[0]:GetNumSeedlings(2) > 5 then

-- checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),attackable[0])


-- end


-- end
-- how many player seedlings are here?

-- more than what we have, and enough to be dangerous.  Also, we do have at least 1 tree here, right?
if checkedroid:GetNumSeedlings(2) < checkedroid:GetNumSeedlings(1) and checkedroid:GetNumSeedlings(1) > 10 and checkedroid:GetNumTrees() > 0 then
-- *** WE OWN THIS ASTEROID, THE ENEMY OUTNUMBER US.  WE HAVE AT LEAST ONE TREE HERE.  We are under attack.  torchmetric 0! ***
torchlit[checkedroid.ID] = 0
-- ***

-- elseif checkedroid:GetNumSeedlings(2) < checkedroid:GetNumSeedlings(1) and checkedroid:GetNumSeedlings(1) > 10 and checkedroid:GetNumTrees() == 0 then
-- *** ITS A FALSE ALARM....but dont try to build here unless the player leaves. ***
--torchlit[checkedroid.ID] = nil
--constructionmetric[checkedroid.ID] = nil
-- but once we get a bit more powerful, we can have a crack at it :>


-- if purgetimer == 0 then
-- purgetimer = GetGameTime() + 30
-- end

-- if GetGameTime() > purgetimer then


-- torchlit[checkedroid.ID] = 0

--for purge = 0,roidnumber do
-- if GetEmpire(2):OwnsAsteroidID(purge) == true and torchlit[purge] == nil and GetGameTime() > purgetimer + 45 then

--GetAsteroid(purge):SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(1),checkedroid)


-- if rcolour < 105 then
-- rcolour = rcolour + 150
-- end
-- end

-- end

-- purgetimer = 0

-- end

-- ***



--  more than zero but less than what we have
elseif checkedroid:GetNumSeedlings(1) > 0 and checkedroid:GetNumSeedlings(2) > checkedroid:GetNumSeedlings(1) then
-- *** WE OWN THIS ASTEROID, THERE ARE ENEMIES BUT WE OUTNUMBER THEM.  Not elligible for sending reinforcements to other asteroids, but not in serious danger either. ***
-- ***


-- No enemies here at all.
elseif checkedroid:GetNumSeedlings(1) < 1 then

-- how many of our seedlings are here?
if checkedroid:GetNumSeedlings(2) > 1 then
-- 1 or more


--how many trees are here?
if checkedroid:GetNumTrees() < checkedroid.TreeCap and checkedroid:GetNumSeedlings(2) > 9 then
-- less than four trees
-- *** WE OWN THIS ASTEROID, THERE ARE NO ENEMIES HERE.  WE HAVE AT LEAST 10 SEEDLINGS AND LESS THAN 4 TREES.  Plant a tree. ***


-- but... is the enemy building up a force nearby, ready to take our freshly built trees..?

danger[checkedroid.ID] = false

for ooo = 0,roidnumber do
if GetEmpire(1):OwnsAsteroidID(ooo) and GetAsteroid(ooo):GetSendDistance() > math.sqrt(((CoordX[ooo] - CoordX[checkedroid.ID])^2) + ((CoordY[ooo] - CoordY[checkedroid.ID])^2)) then

if GetAsteroid(ooo):GetNumSeedlings(1) > 150 then

danger[checkedroid.ID] = true
dangertimer[checkedroid.ID] = GetGameTime()

end

end
end

if danger[checkedroid.ID] == true and checkedroid:GetNumSeedlings(2) > 12 then

checkedroid:PlantDysonTree(2)

elseif danger[checkedroid.ID] == false and GetGameTime() > dangertimer[checkedroid.ID] + 5 then

checkedroid:PlantDysonTree(2)

end
-- ***



elseif checkedroid:GetNumTrees() == checkedroid.TreeCap then
-- max trees.  ELLIGIBLE FOR SENDING REINFORCEMENTS!

if constructionmetric[checkedroid.ID] == 0 then
-- we just built the last tree.  now we should stop advertising to neighbours that we need more seedlings for construction.
constructionmetric[checkedroid.ID] = nil
end


-- is my torch metric nil?

if torchlit[checkedroid.ID] ~= nil then
-- no, my torch metric is not Nil.

-- is my torch metric 0?  Cause, like, I don't have any enemies orbiting me dudez...
if torchlit[checkedroid.ID] == 0 then
-- *** ok, so my torch metric is 0 but there are no enemies here.  Switch all torches off....if other roids are under attack they will just switch theirs on again straight away. ***
-- *** this step is needed to prevent the rest of the torch metrics from spiralling upward out of control.  ***
for g = 0,roidnumber do
torchlit[g] = nil
end
-- ***
else

-- so my torch IS lit, but it's value is NOT zero.  checkedroid is not nil and not 0.

totm = true

for ooo = 0,pathsavailable do

if traversable[ooo] ~= nil then

trav = traversable[ooo]

hopIDint = trav.ID

if torchlit[hopIDint] == 0 and GetGameTime() < 180 then

for emerg = 0,roidnumber do
if GetEmpire(2):OwnsAsteroidID(emerg) == true then
GetAsteroid(emerg):SendSeedlingsToTarget(2,GetAsteroid(emerg):GetNumSeedlings(2),traversable[ooo])
end
end

totm = false


elseif torchlit[hopIDint] == 0 then
-- *** WE HAVE FOUND A NEIGHBOUR THAT HAS A ROUTE TO AN ASTEROID IN NEED OF DEFENCE.  Send seedlings ***

if GetAsteroid(hopIDint):GetNumSeedlings(1) < 10 then
checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),traversable[ooo])

elseif GetAsteroid(hopIDint):GetNumSeedlings(2) > GetAsteroid(hopIDint):GetNumSeedlings(1) * 0.6 then
checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),traversable[ooo])


elseif checkedroid:GetNumSeedlings(2) > (GetEmpire(2).NumSeedlings / 20) then
checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),traversable[ooo])
end

totm = false
torchlit[checkedroid.ID] = 1

-- elseif torchlit[checkedroid.ID] == nil then

-- totm = false
-- torchlit[checkedroid.ID] = torchlit[hopIDint] + 1
-- shortestpath = hopIDint


elseif torchlit[hopIDint] == nil then

-- do nothing, maybe there's no more attack and the metric should be turned off.



-- my torch metric is already non-nil, so set mine to be his + 1 if he has a shorter path than me.
elseif torchlit[hopIDint] < torchlit[checkedroid.ID] - 1 then
totm = false
torchlit[checkedroid.ID] = torchlit[hopIDint] + 1
shortestpath = hopIDint
checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),traversable[ooo])


elseif torchlit[hopIDint] == torchlit[checkedroid.ID] - 1 then

totm = false
torchlit[checkedroid.ID] = torchlit[hopIDint] + 1
shortestpath = hopIDint
checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),traversable[ooo])



end

end
end







-- *** WE OWN THIS ASTEROID.  THERE ARE NO ENEMIES HERE.  WE HAVE AT LEAST 10 SEEDLINGS AND MAXIMUM TREES.  OUR TORCH METRIC INDICATES ***
-- *** THAT A NEARBY ASTEROID REQUIRES SEEDLINGS FOR DEFENSE.  Find a neighbour with the lower metric and send 10 seedlings there.  ***



end



elseif torchlit[checkedroid.ID] == nil then

-- yes my torch metric is nil, now lets see if any neighbours have a torch metric of 0..

totm = true



for ooo = 0,pathsavailable do

if traversable[ooo] ~= nil then

trav = traversable[ooo]

hopIDint = trav.ID



if torchlit[hopIDint] == 0 then
-- *** WE HAVE FOUND A NEIGHBOUR THAT NEEDS MORE SEEDS TO DEFEND WITH.  Send 10 seedlings, or more if we have them available. ***
checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),traversable[ooo])
totm = false
torchlit[checkedroid.ID] = 1



elseif torchlit[hopIDint] ~= nil then
if torchlit[hopIDint] > 0 then
-- seedlings needed in this direction!

-- my torch metric is nil at the moment, so set it to his + 1.
if torchlit[checkedroid.ID] == nil then
totm = false
torchlit[checkedroid.ID] = torchlit[hopIDint] + 1
shortestpath = hopIDint
end

-- my torch metric is already non-nil, so set mine to be his + 1 if he has a shorter path than me.
if torchlit[hopIDint] < torchlit[checkedroid.ID] - 1 then
totm = false
torchlit[checkedroid.ID] = torchlit[hopIDint] + 1
shortestpath = hopIDint
end



end



end

end
end

-- send seeds along the shortest torch path
if torchlit[checkedroid.ID] ~= nil then
if torchlit[checkedroid.ID] > 1 then

checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),GetAsteroid(shortestpath))
end
end

if totm == true and torchlit[checkedroid.ID] ~= nil then
torchlit[checkedroid.ID] = nil
end


-- check if we are still under attack on any asteroids
stillattacked = false

for o = 0,roidnumber do
if GetAsteroid(o):GetNumTrees() > 0 and GetAI(2):OwnsAsteroidID(o) and GetAsteroid(o):GetNumSeedlings(1) > 1 then
stillattacked = true
end
end

if stillattacked == false then
for l = 0,roidnumber do
torchlit[l] = nil
end
end



-- yes, my torch metric is Nil and so is all my neighbours' - check my construction metric next.

-- turn off construction metric
tocm = true

for cc = 0,pathsavailable do

if traversable[cc] ~= nil then

trav = traversable[cc]

hopIDint = trav.ID

if constructionmetric[hopIDint] == 0 and GetAsteroid(hopIDint):GetNumSeedlings(1) < 8 then
-- *** WE HAVE FOUND A NEIGHBOUR THAT NEEDS MORE SEEDS TO BUILD WITH.  Send 10 seedlings, or more if we have them available. ***
checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),traversable[cc])
tocm = false
constructionmetric[checkedroid.ID] = 1


elseif danger[checkedroid.ID] == true or GetGameTime() < (dangertimer[checkedroid.ID] + 90) then

-- *** WAIT A BIT... THERE'S STILL A BIG FORCE NEARBY ***

tocm = false
constructionmetric[checkedroid.ID] = 1




elseif constructionmetric[hopIDint] ~= nil then
if constructionmetric[hopIDint] >= 0 then
-- seedlings needed in this direction!

-- my construction metric is nil at the moment, so set it to his + 1.
if constructionmetric[checkedroid.ID] == nil then
tocm = false
constructionmetric[checkedroid.ID] = constructionmetric[hopIDint] + 1
shortestpath = hopIDint
end

-- my construction metric is already non-nil, so set mine to be his + 1 if he has a shorter path than me.
if constructionmetric[hopIDint] < constructionmetric[checkedroid.ID] - 1 then
tocm = false
constructionmetric[checkedroid.ID] = constructionmetric[hopIDint] + 1
shortestpath = hopIDint
end



end



end

end
end

-- send seeds along the shortest construction path
if constructionmetric[checkedroid.ID] ~= nil then
if constructionmetric[checkedroid.ID] > 1 then

checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),GetAsteroid(shortestpath))
end
end

if tocm == true and constructionmetric[checkedroid.ID] ~= nil then
constructionmetric[checkedroid.ID] = nil
end


-- check if we are finished building trees on all asteroids
stillbuilding = false

for o = 0,roidnumber do
if GetAsteroid(o):GetNumTrees() < GetAsteroid(o).TreeCap and GetAI(2):OwnsAsteroidID(o) then
stillbuilding = true
end
end

if stillbuilding == false then
for l = 0,roidnumber do
constructionmetric[l] = nil
end
end


end

-- ok, we didn't hit on torch metric or construction metric.  So lets do some gathering instead.

if torchlit[checkedroid.ID] == nil and constructionmetric[checkedroid.ID] == nil and GetGameTime() > 6 then
-- is there already a gather point?
-- gatherexists = 0
gathertrue = false
for h = 0,roidnumber do
if gathermetric[h] == 0 then
gatherexists = 1
gathertrue = true
end
end

if gathertrue == false then
gatherexists = 0
end


-- there's no gather point at the moment.
if gatherexists == 0 then


-- ok, is there at least one player-owned asteroid and one traversable asteroid within my send distance?
if pathsavailable > 0 and attackpaths > 0 then
-- yep.
-- *** THEN I'M THE NEW GATHER POINT!! Bringin all the seedlings to the yard, y0. ***
gathermetric[checkedroid.ID] = 0
gatherexists = 1

-- MessageBox("new gather point")
--MessageBox(checkedroid.ID)


else
-- nope.



-- *** I CAN'T BE THE GATHER POINT because I am totally surrounded by either all friendly neighbours or all enemy asteroids. ***

end



if gathermetric[checkedroid.ID] ~= nil then
-- no, my gather metric is not Nil.
-- so my torch IS lit, but it's value is NOT zero.

for ddd = 0,pathsavailable do

if traversable[ddd] ~= nil then

trav = traversable[ddd]

hopIDint = trav.ID

if gathermetric[hopIDint] == 0 then
-- *** WE HAVE FOUND A NEIGHBOUR THAT HAS A ROUTE TO AN ASTEROID IN NEED OF SEEDS.  Send 10 seedlings, or more if we have them available. ***
checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),traversable[ddd])

gathermetric[checkedroid.ID] = 1

electiontimer = GetGameTime()



elseif gathermetric[hopIDint] ~= nil then
if gathermetric[hopIDint] > 0 then
-- both checkedroid and the neighbour we are looking at have a gather metric set.  Also, the neighbour has a metric of at least 1.
-- seedlings needed in this direction!


-- my gather metric is already non-nil, so set mine to be his + 1 if he has a shorter path than me.
if gathermetric[hopIDint] < gathermetric[checkedroid.ID] - 1 then

gathermetric[checkedroid.ID] = gathermetric[hopIDint] + 1
shortestpath = hopIDint


elseif gathermetric[hopIDint] == gathermetric[checkedroid.ID] - 1 then

if shortestpath == nil then
shortestpath = hopIDint
end

if gathermetric[shortestpath] ~= nil then
if gathermetric[hopIDint] < gathermetric[shortestpath] then
shortestpath = hopIDint
end
end

end






end



end

end


end

if gathermetric[checkedroid.ID] ~= nil and gathermetric[shortestpath] ~= nil then

if gathermetric[shortestpath] < gathermetric[checkedroid.ID] then

checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),GetAsteroid(shortestpath))

end

end


-- *** WE OWN THIS ASTEROID.  THERE ARE NO ENEMIES HERE.  WE HAVE AT LEAST 10 SEEDLINGS AND MAXIMUM TREES.  OUR TORCH METRIC INDICATES ***
-- *** THAT A NEARBY ASTEROID REQUIRES SEEDLINGS FOR DEFENSE.  Find a neighbour with the lower metric and send 10 seedlings there.  ***

elseif gathermetric[checkedroid.ID] == nil then

-- yes my gather metric is nil, now lets see if any neighbours have a gather metric of 0..



for ddd = 0,pathsavailable do

if traversable[ddd] ~= nil then

trav = traversable[ddd]

hopIDint = trav.ID

if gathermetric[hopIDint] == 0 then
-- *** WE HAVE FOUND A NEIGHBOUR THAT IS A GATHER POINT.  Send 10 seedlings, or more if we have them available. ***
checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),traversable[ddd])

gathermetric[checkedroid.ID] = 1

elseif gathermetric[hopIDint] ~= nil then
if gathermetric[hopIDint] > 0 then
-- seedlings needed in this direction!

-- my gather metric is nil at the moment, so set it to his + 1.

if shortestpath == nil then
shortestpath = hopIDint
end

gathermetric[checkedroid.ID] = gathermetric[hopIDint] + 1


end



end

end
end



end



-- a gather point already exists.
else





-- Am I the gather point?

if gathermetric[checkedroid.ID] == 0 then
-- I AM the gather point! :>

-- is there still at least 1 player asteroid and 1 friendly asteroid nearby?
--validpath = 0
--validattack = 0
--for i = 0,pathsavailable do
-- if traversable[i] ~= nil then
-- travv = traversable[i]
-- travvy = travv.ID

-- if GetAI(2):OwnsAsteroidID(travvy) == true then
-- there's at least one friendly asteroid nearby.
-- validpath = 1
-- end

-- if GetAI(2):OwnsAsteroidID(travvy) == false then
-- there's at least one player asteroid nearby.
-- validattack = 1
-- end
-- end
--end

if attackpaths > 0 and pathsavailable > 0 then
-- yes, there's at least 1 friendly and 1 enemy asteroid nearby.


-- add up all the player seedlings in the nearby enemy systems.  Do I have at least that, plus 50?

totalseeds = 50
currentlowest = 9000
attackpaths = attackpaths - 1
for j = 0,attackpaths do

totalseeds = totalseeds + attackable[j]:GetNumSeedlings(1)

-- this bit checks which of the asteroids has the lowest number of seedlings.
if attackable[j]:GetNumSeedlings(1) < currentlowest then
currentlowest = attackable[j]:GetNumSeedlings(1)
RAPETARGET = attackable[j]
end



end



-- "if i have more seeds than the enemy does, then..."
if checkedroid:GetNumSeedlings(2) > totalseeds + (RAPETARGET:GetNumDysonTrees() * 5) + (RAPETARGET:GetNumDefenseTrees() * 23) then

if checkedroid:GetNumSeedlings(1) < 15 then
checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),RAPETARGET)

if rcolour < 105 then
rcolour = rcolour + 150
end
end


--MessageBox("Rape time!")
-- *** RAEP TIEM!!!"!"

elseif checkedroid:GetNumSeedlings(2) > 1000 and checkedroid:GetNumSeedlings(1) == 0 then
-- if i have silly numbers of seeds, just gogo anyway.

if checkedroid:GetNumSeedlings(1) < 15 then
checkedroid:SendSeedlingsToTarget(2,checkedroid:GetNumSeedlings(2),RAPETARGET)

if rcolour < 105 then
rcolour = rcolour + 150
end
end

else
-- *** Hold, precious... we have not enough seeds yet to launch an attack... ***






-- ***
end

-- this bit checks if we have a "Cold War" situation on our hands - if so, we'd be better off moving to a different gather point with less player defenses nearby.
-- if totalseeds > 300 and totalseeds > checkedroid:GetNumSeedlings(2) and GetEmpire(1):GetNumOwnedAsteroids() > attackpaths + 1 then
-- MessageBox("ColdWar Detected - reset gather point")
-- for gathreset = 0,roidnumber do
-- gathermetric[gathreset] = nil
-- end
-- gatherexists = 0
-- end

else
-- *** I AM NO LONGER A SUITABLE GATHER POINT.  RESET ALL GATHER POINTS
for r = 6,roidnumber do
gathermetric[r] = nil

end
gatherexists = 0
-- ***

end

else
-- I am not the gather point. :<
-- so send some seedlings to whichever traversable asteroid has the lowest gather metric.

-- ADD SOEM CODE HERE DRUIDS



if gathermetric[checkedroid.ID] ~= nil then
-- no, my gather metric is not Nil.
-- so my torch IS lit, but it's value is NOT zero.

for ddd = 0,pathsavailable do

if traversable[ddd] ~= nil then

trav = traversable[ddd]

hopIDint = trav.ID

if gathermetric[hopIDint] == 0 then
-- *** WE HAVE FOUND A NEIGHBOUR THAT HAS A ROUTE TO AN ASTEROID IN NEED OF SEEDS.  Send 10 seedlings, or more if we have them available. ***
checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),traversable[ddd])

gathermetric[checkedroid.ID] = 1

electiontimer = GetGameTime()



elseif gathermetric[hopIDint] ~= nil then
if gathermetric[hopIDint] > 0 then
-- both checkedroid and the neighbour we are looking at have a gather metric set.  Also, the neighbour has a metric of at least 1.
-- seedlings needed in this direction!


-- my gather metric is already non-nil, so set mine to be his + 1 if he has a shorter path than me.
if gathermetric[hopIDint] < gathermetric[checkedroid.ID] - 1 then

gathermetric[checkedroid.ID] = gathermetric[hopIDint] + 1
shortestpath = hopIDint
checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),traversable[ddd])

elseif gathermetric[hopIDint] == gathermetric[checkedroid.ID] - 1 then

if shortestpath == nil then
shortestpath = hopIDint
end

if gathermetric[shortestpath] ~= nil then
if gathermetric[hopIDint] < gathermetric[shortestpath] then
shortestpath = hopIDint
end
end

checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),traversable[ddd])

end






end



end

end


end

checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),GetAsteroid(shortestpath))


-- *** WE OWN THIS ASTEROID.  THERE ARE NO ENEMIES HERE.  WE HAVE AT LEAST 10 SEEDLINGS AND MAXIMUM TREES.  OUR TORCH METRIC INDICATES ***
-- *** THAT A NEARBY ASTEROID REQUIRES SEEDLINGS FOR DEFENSE.  Find a neighbour with the lower metric and send 10 seedlings there.  ***

elseif gathermetric[checkedroid.ID] == nil then

-- yes my gather metric is nil, now lets see if any neighbours have a gather metric of 0..



for ddd = 0,pathsavailable do

if traversable[ddd] ~= nil then

trav = traversable[ddd]

hopIDint = trav.ID

if gathermetric[hopIDint] == 0 then
-- *** WE HAVE FOUND A NEIGHBOUR THAT IS A GATHER POINT.  Send 10 seedlings, or more if we have them available. ***
checkedroid:SendSeedlingsToTarget(2,((checkedroid:GetNumSeedlings(2)/pathsavailable) + 10),traversable[ddd])

gathermetric[checkedroid.ID] = 1

elseif gathermetric[hopIDint] ~= nil then
if gathermetric[hopIDint] > 0 then
-- seedlings needed in this direction!

-- my gather metric is nil at the moment, so set it to his + 1.

if shortestpath == nil then
shortestpath = hopIDint
end

gathermetric[checkedroid.ID] = gathermetric[hopIDint] + 1


end



end

end
end



end




end

end


end

end

end





-- how many of our seedlings are here?
if checkedroid:GetNumSeedlings(2) < 10 then
-- 9 or less

-- how many trees are here?
if checkedroid:GetNumTrees() < checkedroid.TreeCap then
-- less than four trees

-- *** WE OWN THIS ASTEROID, THERE ARE NO ENEMIES HERE.  WE HAVE LESS THAN 10 SEEDLINGS AND LESS THAN 4 TREES.  We need seedlings for more trees!  Advertise to neighbours that we need some.
constructionmetric[checkedroid.ID] = 0

-- ***
end



end






end







else
-- *** THIS ROID IS NOT OURS, it's a PLAYER asteroid!!  Do nothing with it.  :> ***

-- ***
end

if GetEmpire(1):GetNumOwnedAsteroids() < 4 and GetEmpire(2).NumSeedlings > 800 and finality == false then
finality = true
for doom = 0,roidnumber do
if GetEmpire(2):OwnsAsteroidID(doom) then
GetAsteroid(doom):SendSeedlingsToTarget(2,GetAsteroid(doom):GetNumSeedlings(2),GetEmpire(1):GetRandomAsteroid())
end
end
end



if electiontimer ~= nil then
if GetGameTime() > electiontimer + 45 then
-- we haven't managed to send any seedlings to a gather point for 45 seconds now.  Lets try resetting all the gather metrics to trigger a new gather point election.
for greset = 6,roidnumber do
gathermetric[greset] = nil

end
--MessageBox("Due to low activity a new gather point election was held")
-- lets see if this helps, and seeds now start moving towards the gather point.  if it doesn't, hold a new gather point election in 15 seconds.
electiontimer = electiontimer + 45
gatherexists = 0

end

end


gatherzero = false

for gatherzerocheck = 0,roidnumber do
if gathermetric[gatherzerocheck] == 0 then
gatherzero = true
end
end


if gatherzero == false then
gatherexists = 0
end



if GetEmpire(2).NumSeedlings > 8000 and GetEmpire(2):GetNumOwnedAsteroids() > roidnumber / 2 and endfinal == false then
endfinal = true
for final = 0,roidnumber do

if GetEmpire(2):OwnsAsteroidID(final) then

GetAsteroid(final):SendSeedlingsToTarget(2,GetAsteroid(final):GetNumSeedlings(2),GetEmpire(1):GetRandomAsteroid())

end
end
end


end


-- *** END INFECTED AI ENGINE *** --





coroutine.yield()
end

end

Sniped50

  • Sapling
  • **
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 97
Re: Asteroid collisions
« Reply #23 on: December 21, 2010, 07:36:56 AM »
I've had a look. It doesn't look finished but...

DAMN. IT LOOKS FRICKING AWESOME! :o :o :o :D
Can't wait to see what it'd be like when it IS finished! The bounding box is also a nice touch...

I can't believe you managed to (sort of) get it working! Thank you!

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #24 on: December 21, 2010, 08:13:35 AM »
Glad you like it.  :> Wait till you see the other stuff I'm working on!

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #25 on: December 23, 2010, 12:53:19 AM »
I've tried putting the Infected AI v2 engine into this map, and it works a treat.  :>
There is one minor bug with the AI behaviour so far, but the performance is miles better than v1.

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #26 on: January 01, 2011, 02:48:16 PM »
I'm currently bouncing after the asteroids have already overlapped.  What I really need to do is reverse them by just the right amount so that they are _just_ touching, but not overlapping.  I need to move them backward to those positions, then carry out the bounce from there.  :>

Wonder how I do that. :>

I had a brainwave tonight (er, this morning..) and figured out how to do his.


Some preparation was necessary.  At various points I had to record the existing momentums and coords for both roids, so that they can be restored following a modification.

Here's an explanation of how I did it:

1.  Basically I realised I needed to find two values: The amount the asteroids moved in the latest frame which resulted in a collision, and the amount by which this movement caused them to overlap with each other.

2.  Dividing the latter by the former always gives you a value between 0 and 1, which I have decided to refer to as the ratio of overlap.  This ratio is crucial to the calculations that follow.


3.  So next, I move the asteroid back to where it was the previous frame, recording the current momentums.

4.  Then I move both asteroids forward by momentum * (1 - ratio of overlap), and this brings them to the _exact_ point where the two asteroids touch.

5.  I restore the original momentum values from step 3 in preparation for the bounce.

6.  The bounce calculation is carried out - and it is an "ideal bounce".  The bounce results in new momentum values for both asteroids.

7.  The new momentums are saved.

8.  The momentum is then multiplied by the ratio of overlap.  This gives the remainder of the movement that occurs for this frame, _after_ the "ideal bounce".

9.  Then the asteroids are moved forward in the resultant new direction by whatever amount of ratio * momentum is leftover after the bounce.

10. Then the saved momentums from step 7 are restored.


At this point, the perfect bounce is complete and normal acceleration/movement/gravity calculations can continue.



This was not simple.  :>  But it looks excellent...  Moving asteroids do not get stuck inside each other anymore.  They bounce perfectly every time.

However, this has introduced a new problem.  Due to the screen wrapping, eventually all the asteroids clump up together in the middle!  This is not an incorrect result though - they would naturally do that.  The behaviour simply falls out of the equations naturally.
I will try experimenting to make the asteroids bounce away from each other with more force than they collided with.  Maybe that will prevent the clumping behaviour.
« Last Edit: January 01, 2011, 02:55:16 PM by annikk.exe »

pigpenguin

  • Seedling
  • **
  • Thank You
  • -Given: 0
  • -Receive: 0
  • Posts: 37
Re: Asteroid collisions
« Reply #27 on: January 01, 2011, 05:02:09 PM »
So lets see here that is 3 new mechanics you managed to add to a game with using only scripts in the map editor they got their modding better then most games do. They also have some very creative smart people working on it. Its way over my head atm so I just came to compliment :P

annikk.exe

  • Achiever
  • Ent
  • ****
  • Thank You
  • -Given: 0
  • -Receive: 4
  • Posts: 1,809
Re: Asteroid collisions
« Reply #28 on: January 01, 2011, 09:03:07 PM »
Yep, it's pretty damn powerful :>  That's why I enjoy it so much...the massive creative freedom..
One of the main reasons I've been able to do all this, though, is because of the sympathetic ears of Alex and Rudolf who have actually added scripting commands by request - which is an amazing thing.  For example it didn't used to be possible to move asteroids, but now thanks to their support that is possible.  A gravity engine would have been out of the question had it not been for their kindly intervention.  :>  I think it's important to remember that the game would not exist in the first place without them.

It's hugely satisfying when you get a new mechanic working.  I'm going to release a new level soon, which will include the gravity engine with ideal bounce, parallax scrolling, and AIv2.  That's enough new features to justify a new Annikk map, I think. :>

Bonobo

  • Achiever
  • Old Oak
  • ****
  • Thank You
  • -Given: 139
  • -Receive: 12
  • Posts: 670
  • Eufloria: Yes
Re: Asteroid collisions
« Reply #29 on: January 01, 2011, 10:17:06 PM »
[..] I think it's important to remember that the game would not exist in the first place without them.
Yes, and very nice of you to honour them.

Quote
It's hugely satisfying when you get a new mechanic working.  I'm going to release a new level soon, which will include the gravity engine with ideal bounce, parallax scrolling, and AIv2.  That's enough new features to justify a new Annikk map, I think. :>
Haha, as if ANY of your maps had ever needed any justification :D all they ever needed was … making them weaker, so that lowly sub-average gamers like me could win them ;D