| 12-01-2007, 08:24 PM | #1 | |||
SIMPLE ENTITY ENGINE ******************************** Version 2.4 The third system of its kind created by me, this one will hopefully be more succesful and will live up to my expectations. The two others were Simple Particle System, and then Simple Particle System Mark II. The first of the three was buggy, very slow, and extremely inaccurate. The second used procedural-style coding, which people didn't seem to appreciate. I finally created this: SEE - Simple Entity Engine. It uses object-oriented style scripting, which makes things alot easier to read and understand. I've worked a lot on getting this system to where I want it to be. Please only report bugs or give criticism RELATED to this system! This system requires JassNewGenPack for the use of vJass syntax! This system uses UnitUserData! Quote:
Credits go to HINDYhat for the creation of this system. Thanks to Earth-Fury for helping me a lot and teaching me how to use object-oriented programming. Also thanks to him for coming up with this system's neat name, SEE: Simple Entity Engine. ------------------------ Thanks to PurplePoot for teaching me some things in Jass. ------------------------ Thanks to moyack for helping me on a nagging collision problem, and for being nice and helpful. :D ------------------------ Many thanks to grim001 for helping with a couple of bugs in collision detection. ------------------------ Thanks to anyone and everyone who might use this system in a map of theirs. And now the code: SEE core:library SEEcore requires SEEentityData globals private constant integer DUMMY_ID = 'e000' public constant real FPS = 40.0 private constant real GRAVITY_ACCELERATION = -3000.0 private constant real AIR_DENSITY = 0.001 private constant integer REST_TALLY = 30 private constant real REST_STATE_SPD = 60.0 private constant real COLL_ALGO_SPD = 300.0 private constant real THRESHOLD_COLL_SPD = 70.0 private constant real THRESHOLD_SLIDE_SPD = 100.0 private constant real SAMPLE_RADIUS = 32.0 private constant real MAX_TERRAIN_HEIGHT = 1230.500 endglobals globals public real minX public real maxX public real minY public real maxY private entity enumEntity private boolexpr filter private real RADIUS = 0.0 private real array tempReal private group G = CreateGroup() private integer I endglobals private function enum takes nothing returns boolean local entity e = entity(GetUnitUserData(GetFilterUnit())) return e.data.collideable and not IsUnitInGroup(e.dummy, enumEntity.collGroup) and ( (enumEntity.zP + enumEntity.data.radius + enumEntity.zV >= e.zP - e.data.radius + e.zV) or (enumEntity.zP - enumEntity.data.radius + enumEntity.zV >= e.zP + e.data.radius + e.zV) ) endfunction private function main takes nothing returns nothing local integer i = 0 // Iterator local entity e // Dummy entity used in the main loop. local entity c // Dummy entity used in collisions. local real r local real r2 local real s = 0.0 local real px local real py local real pz = 0.0 local real vx = 0.0 local real vy = 0.0 local real vz = 0.0 loop exitwhen i >= entity.COUNT set e = entity.ENTITY[i] // -- // The following only exectutes if timed life is activated if e.timedLife > -1.0 then set e.timedLife = e.timedLife - 1.0/FPS if e.timedLife <= 0.0 then call e.data.onDeath(e) call e.remove(e.wantRemoveUnit) endif endif // -- call e.data.onLoop(e) if not e.data.affected then set px = GetUnitX(e.dummy) set py = GetUnitY(e.dummy) call MoveLocation(entity.tloc, px, py) set pz = GetUnitFlyHeight(e.dummy) + GetLocationZ(entity.tloc) - e.data.oZ if px != e.xP or py != e.yP or pz != e.zP then set e.restCount = 0 set e.xV = px - e.xP set e.yV = py - e.yP set e.zV = pz - e.zP set e.xP = px set e.yP = py set e.zP = pz endif endif if e.restCount < REST_TALLY then set s = SquareRoot(e.xV*e.xV + e.yV*e.yV + e.zV*e.zV) if s <= REST_STATE_SPD/FPS then set e.restCount = e.restCount + 1 if e.restCount >= REST_TALLY then set e.xV = 0.0 set e.yV = 0.0 set e.zV = 0.0 set s = 0.0 call e.data.onRest(e) endif else set e.restCount = 0 endif if e.data.affected then // -- // The following involves calculations for spring constraints if e.constrained != 0 then set vx = e.xP - e.constrained.xP set vy = e.yP - e.constrained.yP set vz = e.zP - e.constrained.zP set r = SquareRoot(vx*vx + vy*vy + vz*vz) + 0.01 set r = -e.constraintConstant*(1 - e.constraintLength/r) set vx = (vx*r - (e.xV - e.constrained.xV)*e.constraintFriction)/(e.data.mass*FPS*FPS) set vy = (vy*r - (e.yV - e.constrained.yV)*e.constraintFriction)/(e.data.mass*FPS*FPS) set vz = (vz*r - (e.zV - e.constrained.zV)*e.constraintFriction)/(e.data.mass*FPS*FPS) set e.restCount = 0 set e.xV = e.xV + 1000.0*vx set e.yV = e.yV + 1000.0*vy set e.zV = e.zV + 1000.0*vz if e.constrained.data.affected then set e.constrained.restCount = 0 set e.constrained.xV = e.constrained.xV - 1000.0*vx set e.constrained.yV = e.constrained.yV - 1000.0*vy set e.constrained.zV = e.constrained.zV - 1000.0*vz endif endif // -- // -- // The following involves air drag calculations and position/velocity updating set px = 1.0 - AIR_DENSITY*s*e.data.radius*e.data.radius*e.data.drag/(e.data.mass + 0.001) set e.xV = e.xV*px + e.xA set e.yV = e.yV*px + e.yA set e.zV = e.zV*px + e.zA set e.xP = e.xP + e.xV set e.yP = e.yP + e.yV set e.zP = e.zP + e.zV // -- // -- // The following is a check for if the entity is within map bounds if e.xP < minX or e.yP < minY or e.xP > maxX or e.yP > maxY then call e.data.onExit(e) endif // -- if e.zP <= MAX_TERRAIN_HEIGHT then // -- // The following gets the terrain normal <vx, vy, SAMPLE_RADIUS> // and checks to see if the concerned entity sphere is colliding // with the terrain plane. call MoveLocation(entity.tloc, e.xP - SAMPLE_RADIUS, e.yP) call MoveLocation(entity.tloc2, e.xP + SAMPLE_RADIUS, e.yP) set vx = GetLocationZ(entity.tloc) - GetLocationZ(entity.tloc2) call MoveLocation(entity.tloc, e.xP, e.yP - SAMPLE_RADIUS) call MoveLocation(entity.tloc2, e.xP, e.yP + SAMPLE_RADIUS) set vy = GetLocationZ(entity.tloc) - GetLocationZ(entity.tloc2) set vz = vx*vx + vy*vy + SAMPLE_RADIUS*SAMPLE_RADIUS call MoveLocation(entity.tloc, e.xP, e.yP) set r = e.data.radius/SquareRoot(vz) - (e.zP - GetLocationZ(entity.tloc))*SAMPLE_RADIUS/vz // -- if r >= 0.0 then // -- // Moves the entity to the touching point between the // entity and the terrain plane. set e.xP = e.xP + r*vx set e.yP = e.yP + r*vy set e.zP = e.zP + r*SAMPLE_RADIUS // -- // -- // Gets the projection of the entity's velocity onto the // terrain normal: <px, py, pz> set vz = (e.xV*vx + e.yV*vy + e.zV*SAMPLE_RADIUS)/vz set px = vx*vz set py = vy*vz set pz = SAMPLE_RADIUS*vz // -- if px*px + py*py + pz*pz <= THRESHOLD_SLIDE_SPD*THRESHOLD_SLIDE_SPD/(FPS*FPS) or not(e.data.bounceable) then // -- // Calculates the entity's new velocity after impact. This script // scales the velocity component parallel to the terrain plane by // taking in account friction, and completely removes the // perpendicular component. This gives a sliding effect. set e.xV = (e.xV - px)*(1.0 - e.data.friction) set e.yV = (e.yV - py)*(1.0 - e.data.friction) set e.zV = (e.zV - pz)*(1.0 - e.data.friction) // -- else // -- // The parallel component functions as described above. However, // the perpendicular component is added here, and scaled taking // in account restitution. Bouncing is determined by resitution. set e.xV = (e.xV - px)*(1.0 - e.data.friction) - px*e.data.restitution set e.yV = (e.yV - py)*(1.0 - e.data.friction) - py*e.data.restitution set e.zV = (e.zV - pz)*(1.0 - e.data.friction) - pz*e.data.restitution // -- endif set s = SquareRoot(e.xV*e.xV + e.yV*e.yV + e.zV*e.zV) call e.data.onGround(e) endif endif // -- // Rendering the entity based on position and offset call MoveLocation(entity.tloc, e.xP, e.yP) set r = GetLocationZ(entity.tloc) call SetUnitX(e.dummy, e.xP + e.data.oX) call SetUnitY(e.dummy, e.yP + e.data.oY) call SetUnitFlyHeight(e.dummy, e.zP - r + e.data.oZ, 0.0) // -- endif // -- // Below is the collision detection and resolution if e.data.collideable then set enumEntity = e // -- // Enum'ing through the entities in a cylindrical range first; // speed*2.0 is an added term due to a logical constraint (thanks to grim001); // RADIUS is the largest current radius of all entities; // e.collGroup contains entities with which collisions have already been // dealt. Thus, calculating a second collision would be redundant. // Entities are properly sifted considering this. call GroupEnumUnitsInRange(entity.COLL, e.xP, e.yP, e.data.radius + RADIUS + s*2.0, filter) call GroupRemoveUnit(entity.COLL, e.dummy) call GroupClear(e.collGroup) // -- loop set c = entity(GetUnitUserData(FirstOfGroup(entity.COLL))) exitwhen c.dummy == null // -- // Storing of various relative components and dot products set vx = e.xV - c.xV set vy = e.yV - c.yV set vz = e.zV - c.zV set r2 = vx*vx + vy*vy + vz*vz // -- // The following condition evaluates the relative velocity with respect to the collision separator if r2 >= COLL_ALGO_SPD*COLL_ALGO_SPD/(FPS*FPS) then // If the relative velocity is larger than the collision separator, advanced collision detection is executed // The following collision detection was established using the equation: |deltaP + t*deltaV| <= r1 + r2 // Note that at this point, variable names most likely have nothing to do with what they hold set px = e.xP - c.xP set py = e.yP - c.yP set pz = e.zP - c.zP set s = px*vx + py*vy + pz*vz set r = px*px + py*py + pz*pz set vx = e.data.radius + c.data.radius set vy = s*s - r2*(r - vx*vx) if vy >= 0.0 then set vy = (-s - SquareRoot(vy))/r2 if vy > 0.0 and vy <= 1.0 then // -- // The following prepares collision resolution while moving entities to their touching position set c.xP = c.xP + c.xV*vy set c.yP = c.yP + c.yV*vy set c.zP = c.zP + c.zV*vy set e.xP = e.xP + e.xV*vy set e.yP = e.yP + e.yV*vy set e.zP = e.zP + e.zV*vy // -- // -- // The following is code for collision resolution set s = s*(1.0 + c.data.restitution*e.data.restitution)/(r*(c.data.mass + e.data.mass) + 0.001) if c.data.bounceable then set vx = s*e.data.mass set c.xV = c.xV + px*vx set c.yV = c.yV + py*vx set c.zV = c.zV + pz*vx if c.xV*c.xV + c.yV*c.yV + c.zV*c.zV <= THRESHOLD_COLL_SPD*THRESHOLD_COLL_SPD/(FPS*FPS) then // If the entity moves slower than the threshold velocity, set the velocity to 0 set c.xV = 0.0 set c.yV = 0.0 set c.zV = 0.0 else // If the entity begins moving again, its rest has been disturbed set c.restCount = 0 endif endif if e.data.bounceable then set vx = s*c.data.mass set e.xV = e.xV - px*vx set e.yV = e.yV - py*vx set e.zV = e.zV - pz*vx if e.xV*e.xV + e.yV*e.yV + e.zV*e.zV <= THRESHOLD_COLL_SPD*THRESHOLD_COLL_SPD/(FPS*FPS) then set e.xV = 0.0 set e.yV = 0.0 set e.zV = 0.0 else set e.restCount = 0 endif endif // -- call e.data.onCollision(e, c) call c.data.onCollision(c, e) elseif vy < 0.0 and r < vx*vx then set vz = (vx/(SquareRoot(r) + 0.001) - 1.0)/(c.data.mass + e.data.mass + 0.001) set vy = e.data.mass*vz if r*vy*vy > 0.01/(FPS*FPS) then set c.xP = c.xP - vy*px set c.yP = c.yP - vy*py set c.zP = c.zP - vy*pz set c.restCount = 0 endif set vy = c.data.mass*vz if r*vy*vy > 0.01/(FPS*FPS) then set e.xP = e.xP + vy*px set e.yP = e.yP + vy*py set e.zP = e.zP + vy*pz set e.restCount = 0 endif endif endif else set px = e.xP - c.xP set py = e.yP - c.yP set pz = e.zP - c.zP set r = px*px + py*py + pz*pz set r2 = e.data.radius + c.data.radius if r < r2*r2 then // If the relative velocity is smaller than the collision separator; // and if two entities are currently colliding, simple collision script is executed // -- // The following code moves entities apart from eachother based on their mass and the distance between them set s = (px*vx + py*vy + pz*vz)*(1.0 + c.data.restitution*e.data.restitution)/(r*(c.data.mass + e.data.mass) + 0.001) set vz = (r2/(SquareRoot(r) + 0.001) - 1.0)/(c.data.mass + e.data.mass + 0.001) set vy = c.data.mass*vz if r*vy*vy > 0.01/(FPS*FPS) then set e.xP = e.xP + vy*px set e.yP = e.yP + vy*py set e.zP = e.zP + vy*pz set e.restCount = 0 endif set vy = e.data.mass*vz if r*vy*vy > 0.01/(FPS*FPS) then set c.xP = c.xP - vy*px set c.yP = c.yP - vy*py set c.zP = c.zP - vy*pz set c.restCount = 0 endif // -- if c.data.bounceable then set vx = s*e.data.mass set c.xV = c.xV + px*vx set c.yV = c.yV + py*vx set c.zV = c.zV + pz*vx if c.xV*c.xV + c.yV*c.yV + c.zV*c.zV <= THRESHOLD_COLL_SPD*THRESHOLD_COLL_SPD/(FPS*FPS) then set c.xV = 0.0 set c.yV = 0.0 set c.zV = 0.0 else set c.restCount = 0 endif endif if e.data.bounceable then set vx = s*c.data.mass set e.xV = e.xV - px*vx set e.yV = e.yV - py*vx set e.zV = e.zV - pz*vx if e.xV*e.xV + e.yV*e.yV + e.zV*e.zV <= THRESHOLD_COLL_SPD*THRESHOLD_COLL_SPD/(FPS*FPS) then set e.xV = 0.0 set e.yV = 0.0 set e.zV = 0.0 else set e.restCount = 0 endif endif call e.data.onCollision(e, c) call c.data.onCollision(c, e) endif // -- endif call GroupRemoveUnit(entity.COLL, c.dummy) call GroupAddUnit(c.collGroup, e.dummy) endloop endif // -- endif set i = i + 1 endloop // -- // The following destroys all entities added to the destroy stack set i = entity.COUNT_DES loop set i = i - 1 exitwhen i < 0 call entity.ENTITY_DES[i].destroy() endloop set entity.COUNT_DES = 0 // -- endfunction struct entity entityData data unit dummy effect eff real xP real yP real zP real xV = 0.0 real yV = 0.0 real zV = 0.0 real xA = 0.0 real yA = 0.0 real zA = 0.0 trigger onEvent = CreateTrigger() real timedLife = -1.0 integer restCount = 0 integer ID boolean wantRemoveUnit entity constrained = 0 real constraintConstant real constraintLength real constraintFriction group collGroup = CreateGroup() static timer T = CreateTimer() static group COLL = CreateGroup() static integer COUNT = 0 static entity array ENTITY static integer COUNT_DES = 0 static entity array ENTITY_DES static location tloc = Location(0.0, 0.0) static location tloc2 = Location(0.0, 0.0) //////////////////////////////////////////////////////////// ///////////////// __FUNCTIONALITY METHODS__ //////////////// method remove takes boolean wantRemoveUnit returns nothing if this.timedLife != -2.0 then set this.constrained = 0 set this.data.collideable = false set this.wantRemoveUnit = wantRemoveUnit set this.timedLife = -2.0 set entity.ENTITY_DES[entity.COUNT_DES] = this set entity.COUNT_DES = entity.COUNT_DES + 1 endif endmethod method applyTimedLife takes real time, boolean removeUnit returns nothing set this.timedLife = time set this.wantRemoveUnit = removeUnit endmethod method addGravity takes real factor returns nothing set this.zA = GRAVITY_ACCELERATION*factor/(FPS*FPS) endmethod method setVelocity takes real xVel, real yVel, real zVel returns nothing set this.xV = xVel/FPS set this.yV = yVel/FPS set this.zV = zVel/FPS endmethod method addVelocity takes real x, real y, real z returns nothing set this.xV = this.xV + x/FPS set this.yV = this.yV + y/FPS set this.zV = this.zV + z/FPS endmethod method setAcceleration takes real xAcc, real yAcc, real zAcc returns nothing set this.xA = xAcc/(FPS*FPS) set this.yA = yAcc/(FPS*FPS) set this.zA = zAcc/(FPS*FPS) endmethod method addAcceleration takes real x, real y, real z returns nothing set this.xA = this.xA + x/(FPS*FPS) set this.yA = this.yA + y/(FPS*FPS) set this.zA = this.zA + z/(FPS*FPS) endmethod method setEntityFlyHeight takes real z returns nothing call MoveLocation(entity.tloc, this.xP, this.yP) set this.zP = GetLocationZ(entity.tloc) + z endmethod method removeHealthBar takes nothing returns nothing call UnitAddAbility(this.dummy, 'Aloc') call ShowUnit(this.dummy, false) call UnitRemoveAbility(this.dummy, 'Aloc') call ShowUnit(this.dummy, true) endmethod ////////////// __END OF FUNCTIONALITY METHODS__ //////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// /////////////////// __CONSTRAINT METHODS__ ///////////////// method addSpringConstraint takes entity attached, real springConstant, real springLength, real friction returns nothing set this.constrained = attached set this.constraintConstant = springConstant set this.constraintLength = springLength set this.constraintFriction = friction endmethod method removeSpringConstraint takes nothing returns nothing set this.constrained = 0 endmethod //////////////// __END OF CONSTRAINT METHODS__ ///////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// /////////////////// __MOVEMENT METHODS__ /////////////////// method projectWithAngle takes real xyAngle, real zAngle, real speed returns nothing local real cos = Cos(zAngle)*speed/FPS set this.restCount = 0 set this.xV = Cos(xyAngle)*cos set this.yV = Sin(xyAngle)*cos set this.zV = Sin(zAngle)*speed/FPS endmethod method projectToPointTimed takes real xPos, real yPos, real zPos, real time returns nothing set this.restCount = 0 set this.xV = this.xV + (xPos - this.xP)/(time*FPS) set this.yV = this.yV + (yPos - this.yP)/(time*FPS) call MoveLocation(entity.tloc, xPos, yPos) set this.zV = this.zV + (zPos + GetLocationZ(entity.tloc) - this.zP)/(time*FPS) - 0.5*this.zA*FPS*time endmethod method projectToPointTimedEx takes real xPos, real yPos, real zPos, real time, real maxDist returns nothing local real d call MoveLocation(entity.tloc, xPos, yPos) set xPos = xPos - this.xP set yPos = yPos - this.yP set zPos = zPos + GetLocationZ(entity.tloc) - this.zP set d = xPos*xPos + yPos*yPos + zPos*zPos if d > maxDist*maxDist then set d = maxDist/(SquareRoot(d) + 0.01) set xPos = xPos*d set yPos = yPos*d set zPos = zPos*d endif call MoveLocation(entity.tloc, this.xP, this.yP) call this.projectToPointTimed(this.xP + xPos, this.yP + yPos, this.zP + zPos - GetLocationZ(entity.tloc), time) endmethod method projectTowardsPointSpeed takes real xPos, real yPos, real zPos, real speed returns nothing local real dx = xPos - this.xP local real dy = yPos - this.yP local real dz local real d call MoveLocation(entity.tloc, xPos, yPos) set dz = zPos + GetLocationZ(entity.tloc) - this.zP set this.restCount = 0 set d = speed/(FPS*SquareRoot(dx*dx + dy*dy + dz*dz) + 0.001) set this.xV = dx*d set this.yV = dy*d set this.zV = dz*d endmethod method projectToPointSpeed takes real xPos, real yPos, real zPos, real speed, boolean lob returns boolean local real dx = xPos - this.xP local real dy = yPos - this.yP local real d local real des call MoveLocation(entity.tloc, xPos, yPos) set speed = speed/FPS if this.zA != 0.0 then set d = SquareRoot(dx*dx + dy*dy) + 0.001 set des = speed*speed*speed*speed - this.zA*this.zA*d*d + 2*this.zA*speed*speed*(GetLocationZ(entity.tloc) + zPos - this.zP) if des >= 0.0 then set this.restCount = 0 if lob then set des = Atan((-speed*speed-SquareRoot(des))/(this.zA*d)) else set des = Atan((-speed*speed+SquareRoot(des))/(this.zA*d)) endif set this.zV = speed*Sin(des) set des = speed*Cos(des)/d set this.xV = des*dx set this.yV = des*dy return true endif return false else set this.restCount = 0 set d = GetLocationZ(entity.tloc) + zPos - this.zP set des = speed/(SquareRoot(dx*dx + dy*dy + d*d) + 0.001) set this.xV = dx*des set this.yV = dy*des set this.zV = d*des return true endif endmethod method projectToPointSpeedEx takes real xPos, real yPos, real zPos, real speed, boolean lob, real maxDist returns boolean local real d call MoveLocation(entity.tloc, xPos, yPos) set xPos = xPos - this.xP set yPos = yPos - this.yP set zPos = zPos + GetLocationZ(entity.tloc) - this.zP set d = xPos*xPos + yPos*yPos + zPos*zPos if d > maxDist*maxDist then set d = maxDist/(SquareRoot(d) + 0.01) set xPos = xPos*d set yPos = yPos*d set zPos = zPos*d endif call MoveLocation(entity.tloc, this.xP, this.yP) return this.projectToPointSpeed(this.xP + xPos, this.yP + yPos, this.zP + zPos - GetLocationZ(entity.tloc), speed, lob) endmethod method projectToEntityTimed takes entity e, real time returns nothing local real xPos = e.xP + e.xV*time*FPS local real yPos = e.yP + e.yV*time*FPS local real zPos = e.zP + e.zV*time*FPS set this.restCount = 0 set this.xV = (xPos - this.xP)/(time*FPS) set this.yV = (yPos - this.yP)/(time*FPS) set this.zV = (zPos - this.zP)/(time*FPS) - 0.5*this.zA*FPS*time endmethod //////////////// __END OF MOVEMENT METHODS__ /////////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //////////////////// __GROUP METHODS__ ///////////////////// static method isEntityInRangeFilter takes nothing returns boolean local entity e = entity(GetUnitUserData(GetFilterUnit())) local real dx local real dy local real dz if integer(e) != 0 then set dx = e.xP - tempReal[0] set dy = e.yP - tempReal[1] set dz = e.zP - tempReal[2] return dx*dx + dy*dy + dz*dz <= tempReal[3]*tempReal[3] endif return false endmethod static method enumEntitiesInRange takes group g, real x, real y, real z, real range, boolexpr filter returns nothing local boolexpr b = And(filter, Filter(function entity.isEntityInRangeFilter)) debug if filter == null then debug call BJDebugMsg(SCOPE_PREFIX + ": null filter in enumEntitiesInRange") debug endif set tempReal[0] = x set tempReal[1] = y set tempReal[2] = z set tempReal[3] = range call GroupEnumUnitsInRange(g, x, y, range, b) call DestroyBoolExpr(b) set b = null endmethod static method isEntityFilter takes nothing returns boolean return GetUnitUserData(GetFilterUnit()) != 0 endmethod static method enumEntitiesInRect takes group g, rect whichRect, boolexpr filter returns nothing local boolexpr b = And(filter, Filter(function entity.isEntityFilter)) debug if filter == null then debug call BJDebugMsg(SCOPE_PREFIX + ": null filter in enumEntitiesInRect") debug endif call GroupEnumUnitsInRect(g, whichRect, b) call DestroyBoolExpr(b) set b = null endmethod static method isEntityOfTypeFilter takes nothing returns boolean local entity e = GetUnitUserData(GetFilterUnit()) return integer(e) != 0 and e.data.getType() == I endmethod static method enumEntitiesOfType takes group g, integer typeID, boolexpr filter returns nothing local boolexpr b = And(filter, Filter(function entity.isEntityOfTypeFilter)) debug if filter == null then debug call BJDebugMsg(SCOPE_PREFIX + ": null filter in enumEntitiesOfType") debug endif set I = typeID call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, b) call DestroyBoolExpr(b) set b = null endmethod static method enumEntitiesOfPlayer takes group g, player whichPlayer, boolexpr filter returns nothing local boolexpr b = And(filter, Filter(function entity.isEntityFilter)) debug if filter == null then debug call BJDebugMsg(SCOPE_PREFIX + ": null filter in enumEntitiesOfPlayer") debug endif call GroupEnumUnitsOfPlayer(g, whichPlayer, b) call DestroyBoolExpr(b) set b = null endmethod ////////////////// __END OF GROUP METHODS__ ///////////////// ///////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// //////////////////// __CORE METHODS__ ////////////////////// method onDestroy takes nothing returns nothing local integer i = 0 // If the unit is labeled to be removed, the kill it, hide it, and destroy the attached effect. call SetUnitUserData(this.dummy, 0) call DestroyTrigger(this.onEvent) set this.onEvent = null call DestroyGroup(this.collGroup) set this.collGroup = null if this.wantRemoveUnit then call RemoveUnit(this.dummy) call DestroyEffect(this.eff) endif set this.dummy = null set this.eff = null if this.data.radius == RADIUS then loop exitwhen i >= entity.COUNT if entity.ENTITY[i].data.radius > RADIUS then set RADIUS = entity.ENTITY[i].data.radius endif set i = i + 1 endloop endif // Updating the entity stack. set entity.COUNT = entity.COUNT - 1 set entity.ENTITY[this.ID] = entity.ENTITY[entity.COUNT] set entity.ENTITY[this.ID].ID = this.ID if entity.COUNT == 0 then call PauseTimer(entity.T) endif endmethod static method onEvent_Action takes nothing returns boolean local eventid id = GetTriggerEventId() local entity e = GetUnitUserData(GetTriggerUnit()) // Checking all possible eventids to call the correct method. if id == EVENT_UNIT_DEATH then call e.data.onDeath(e) elseif id == EVENT_UNIT_SELECTED then call e.data.onSelect(e) elseif id == EVENT_UNIT_DESELECTED then call e.data.onDeselect(e) elseif id == EVENT_UNIT_ISSUED_ORDER then call e.data.onOrder(e) elseif id == EVENT_UNIT_ISSUED_POINT_ORDER then call e.data.onPointOrder(e) elseif id == EVENT_UNIT_ISSUED_TARGET_ORDER then call e.data.onTargetOrder(e) endif return false endmethod ////////////////// __END OF CORE METHODS__ ///////////////// //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// /////////////////// __CREATION METHODS__ /////////////////// static method create takes entityData data, player owner, real xPos, real yPos, real height, real facing returns entity local entity e = entity.allocate() set e.data = data // Creating dummy unit. set e.dummy = CreateUnit(owner, DUMMY_ID, xPos, yPos, facing) call UnitAddAbility(e.dummy, 'Aloc') call ShowUnit(e.dummy, false) call UnitRemoveAbility(e.dummy, 'Aloc') call ShowUnit(e.dummy, true) if data.scale == -1.0 then set data.scale = 1.0 endif call SetUnitScale(e.dummy, data.scale, data.scale, data.scale) call UnitAddAbility(e.dummy, 'Amrf') call UnitRemoveAbility(e.dummy, 'Amrf') set e.eff = AddSpecialEffectTarget(data.modelPath, e.dummy, "origin") call SetUnitAnimationByIndex(e.dummy, 90) // Initializing position. set e.xP = xPos set e.yP = yPos call MoveLocation(entity.tloc, xPos, yPos) set e.zP = height + GetLocationZ(entity.tloc) call SetUnitFlyHeight(e.dummy, height, data.oZ) // Initializing the onEvent trigger. call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DEATH) if data.collideable then call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_ORDER) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_POINT_ORDER) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_TARGET_ORDER) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_SELECTED) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DESELECTED) else call UnitAddAbility(e.dummy, 'Aloc') endif call TriggerAddCondition(e.onEvent, Filter(function entity.onEvent_Action)) // Updating the entity stack. set e.ID = entity.COUNT set entity.ENTITY[entity.COUNT] = e set entity.COUNT = entity.COUNT + 1 call SetUnitUserData(e.dummy, integer(e)) if entity.COUNT == 1 then call TimerStart(entity.T, 1.0/FPS, true, function main) endif if data.radius > RADIUS then set RADIUS = data.radius endif call data.onCreate(e) return e endmethod static method createFromUnit takes entityData data, unit someUnit returns entity local entity e = entity.allocate() if GetUnitUserData(someUnit) != 0 then debug call BJDebugMsg(SCOPE_PREFIX + ": createFromUnit argument is already an entity/UnitUserData was used") call e.destroy() return 0 endif if data.scale != -1.0 then call SetUnitScale(someUnit, data.scale, data.scale, data.scale) endif set e.data = data // Initializing the entity's unit. set e.dummy = someUnit call UnitAddAbility(someUnit, 'Amrf') call UnitRemoveAbility(someUnit, 'Amrf') // Initializing the entity's position. set e.xP = GetUnitX(someUnit) set e.yP = GetUnitY(someUnit) call MoveLocation(entity.tloc, e.xP, e.yP) set e.zP = GetUnitFlyHeight(someUnit) + GetLocationZ(entity.tloc) + data.radius // Initializing the onEvent trigger. call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DEATH) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_ORDER) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_POINT_ORDER) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_TARGET_ORDER) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_SELECTED) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DESELECTED) call TriggerAddCondition(e.onEvent, Filter(function entity.onEvent_Action)) // Updating the entity stack. set e.ID = entity.COUNT set entity.ENTITY[entity.COUNT] = e set entity.COUNT = entity.COUNT + 1 call SetUnitUserData(someUnit, integer(e)) if entity.COUNT == 1 then call TimerStart(entity.T, 1.0/FPS, true, function main) endif if data.radius > RADIUS then set RADIUS = data.radius endif call data.onCreate(e) return e endmethod static method createAsUnit takes entityData data, player whichPlayer, integer unitID, real x, real y, real height, real facing returns entity local entity e = entity.allocate() set e.dummy = CreateUnit(whichPlayer, unitID, x, y, facing) if data.scale != -1.0 then call SetUnitScale(e.dummy, data.scale, data.scale, data.scale) endif set e.data = data // Initializing the entity's unit. call UnitAddAbility(e.dummy, 'Amrf') call UnitRemoveAbility(e.dummy, 'Amrf') call SetUnitFlyHeight(e.dummy, height, 0.0) // Initializing the entity's position. set e.xP = x set e.yP = y call MoveLocation(entity.tloc, e.xP, e.yP) set e.zP = height + GetLocationZ(entity.tloc) + data.radius // Initializing the onEvent trigger. call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DEATH) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_ORDER) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_POINT_ORDER) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_ISSUED_TARGET_ORDER) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_SELECTED) call TriggerRegisterUnitEvent(e.onEvent, e.dummy, EVENT_UNIT_DESELECTED) call TriggerAddCondition(e.onEvent, Filter(function entity.onEvent_Action)) // Updating the entity stack. set e.ID = entity.COUNT set entity.ENTITY[entity.COUNT] = e set entity.COUNT = entity.COUNT + 1 call SetUnitUserData(e.dummy, integer(e)) if entity.COUNT == 1 then call TimerStart(entity.T, 1.0/FPS, true, function main) endif if data.radius > RADIUS then set RADIUS = data.radius endif call data.onCreate(e) return e endmethod static method createFromGroupChild takes nothing returns nothing call entity.createFromUnit(entityData.create(I), GetEnumUnit()) endmethod static method createFromGroup takes integer entityType, group whichGroup returns nothing set I = entityType call ForGroup(whichGroup, function entity.createFromGroupChild) endmethod static method createFromUnitType takes integer entityType, integer unitID, boolexpr filter returns nothing local boolexpr b = And(filterGetUnitsOfTypeIdAll, filter) set bj_groupEnumTypeId = unitID call GroupEnumUnitsInRect(G, bj_mapInitialPlayableArea, b) set I = entityType call ForGroup(G, function entity.createFromGroupChild) call DestroyBoolExpr(b) set b = null endmethod //////////////// __END OF CREATION METHODS__ /////////////// //////////////////////////////////////////////////////////// private static method onInit takes nothing returns nothing set minX = GetRectMinX(bj_mapInitialPlayableArea) set minY = GetRectMinY(bj_mapInitialPlayableArea) set maxX = GetRectMaxX(bj_mapInitialPlayableArea) set maxY = GetRectMaxY(bj_mapInitialPlayableArea) set filter = Filter(function enum) endmethod //! runtextmacro SEEplugin() endstruct //call proj.projectWithAngle(a*bj_DEGTORAD, Atan(-1500.0*1500.0/(SquareRoot(x*x + y*y)*SEEcore_FPS*SEEcore_FPS*proj.zA)), 1500.0) endlibrary SEE entityData:library SEEentityData interface entityData real radius real mass = 0.0 real drag = 0.0 real restitution = 1.0 real friction = 0.0 real scale = -1.0 boolean collideable = false boolean bounceable = false string modelPath = "" real oX = 0.0 real oY = 0.0 real oZ = 0.0 boolean affected = true method onCreate takes entity e returns nothing defaults nothing method onGround takes entity e returns nothing defaults nothing method onCollision takes entity e, entity e2 returns nothing defaults nothing method onRest takes entity e returns nothing defaults nothing method onSelect takes entity e returns nothing defaults nothing method onDeselect takes entity e returns nothing defaults nothing method onDeath takes entity e returns nothing defaults nothing method onExit takes entity e returns nothing defaults nothing method onPointOrder takes entity e returns nothing defaults nothing method onTargetOrder takes entity e returns nothing defaults nothing method onOrder takes entity e returns nothing defaults nothing method onLoop takes entity e returns nothing defaults nothing endinterface endlibrary Additional triggers, such as the plugin triggers, can be found inside the map. ********************************
And now, documentation:
Constant List: *******************************************
***** ~ SEE Constant List ~ *****
*******************************************
The default values listed here are not static, and should be changed if
a user deems it necessary. They are not perfectly calibrated by any means.
private constant integer DUMMY_ID = 'e000'
-- This represents the unit rawcode of the dummy unit. You should transfer the dummy unit
included in this map to yours, or be sure that your dummy works in the same way as the one
in this map (z-axis orientation by animation index).
public real FPS = 40.0
-- The FPS constant represents the frequency of the SEEcore main function. A higher FPS
means smoother motion, but more processing stress on the system.
private real GRAVITY_ACCELERATION = -3000.0
-- GRAVITY_ACCELERATION is like the gravitational constant g in real life physics. It
represents the downwards acceleration of an object. This means -3000.0 Wc3 units per second
per second.
private constant real AIR_DENSITY = 0.001
-- AIR_DENSITY is the global air density constant used in drag calculations. It's abstract
in this case, as it includes many factors such as pi and 0.5 and others. Consider it an
arbitrary constant.
private constant integer REST_TALLY = 30
-- When an entity is reaching its resting state, this is the amount of iterations of it being
at a resting speed for it to be considered at rest. Higher REST_TALLY indicates more processing
stress on the system, but possibly more precision on resting states.
private constant real REST_STATE_SPD = 60.0
-- This constant represents the maximum speed that an entity can have to still be considered
at rest. If an entity's speed goes above REST_STATE_SPD, then an entity will stop being
considered for a resting state.
private constant real COLL_ALGO_SPD = 300.0
-- COLL_ALGO_SPD represents the collision detection algorithm threshold speed. There are two
different collision algorithms included in SEE. One of them is used when entities move at slow
speeds, and the other when entities move at high speeds. When an entity's speed is below
COLL_ALGO_SPD on collision, then the simple algorithm will run. 300.0 is a good value for this.
private constant real THRESHOLD_COLL_SPD = 70.0
-- THRESHOLD_COLL_SPEED is used in collision resoltion. If an entity bounces off another entity
and it's resulting speed is lower than THRESHOLD_COLL_SPD, then its speed will be set to 0.0.
private constant real THRESHOLD_SLIDE_SPD = 100.0
-- THRESHOLD_SLIDE_SPEED is used in collision resolution with the ground. When a bounceable
entity collides with the ground and its perpendicular component of velocity is smaller than
THRESHOLD_SLIDE_SPD, then its perpendicular component is set to 0.0. Therefore, the only
velocity remaining is parallel to the ground, i.e. the entity is sliding.
private constant real SAMPLE_RADIUS = 32.0
-- SAMPLE_RADIUS is used in terrain collision resolution. SAMPLE_RADIUS is the radius from which
a terrain normal is established. 32.0 is a good value. Try messing around with it if you don't
like the way entities bounce currently, but don't expect much of a difference.
private constant real MAX_TERRAIN_HEIGHT = 1230.500
-- MAX_TERRAIN_HEIGHT is the highest terrain point in your map. The SEEheight library automatically
calculates this value if its trigger is enabled. If you are using very tall walkable destrucables,
then you might have to enter the value manually. Usually not, though. Member List: *******************************************
****** ~ SEE Member List ~ ******
*******************************************
-----------------------------------------------------
*** 1. 'entityData' struct members:
-----------------------------------------------------
real radius
-- The radius is used in collision detection, both entity-entity and ground-entity.
Radius does NOT automatically detect the appropriate scale of a model.
real mass
-- Mass is used in elastic collisions. There is no unit for mass: all entered
mass properties are relative to eachother.
real drag
-- The drag coefficient is related solely to the decelleration caused by air
resistance. The higher the coefficient, the more air resistance.
real restitution
-- The coefficient of restitution is a coefficient of velocities after entity-entity
impact. It's an abstract value. A coeffiecient of 1.0 is perfectly elastic collision.
real friction
-- This is a coefficient to velocity on ground-entity collisions. If friction = 1.0,
then there is no motion. If friction = 0.0, then there is no friction.
real scale
-- This is a coefficient to the model's scale that will be applied upon entity creation.
boolean collideable
-- This boolean determines whether an entity is collideable or not. Useful if you just want particles.
boolean bounceable
-- This boolean determines if an entity should bounce using elastic collisions.
string modelPath
-- modelPath points to a string, which refers to the entity's model, in the case of
the entity.create method. If you are using entity.createFromUnit, this parameter is useless.
real oX
real oY
real oZ
-- The 'o' vector is an offset parameter, meaning that the model's position will be offset
by (oX, oY, oZ) during entity rendering. Useful for things like inanimate objects. This has
been created because entities must not sink into the ground, thus their height can be, at the
least, their radius. In certain cases, this results in a floating model. A negative oZ can fix
this issue.
boolean affected
-- This boolean determines if an entity is affected by gravity or terrain etc...
Basically means that if affected is true, then the entity will behave like an entity.
If affected is false, the entity will behave like a regular unit in Warcraft III: you can
order it and it will move like a regular unit. One important thing to remember is that
velocity is conserved, even if affected is false.
-----------------------------------------------------
*** 2. 'entity' struct members:
-----------------------------------------------------
entityData data
-- 'data' points to the assigned entityData of an entity.
unit dummy
-- This unit is the concerned entity unit. For entity.create method, this points
to the dummy unit, on which is attached an effect.
effect eff
-- This variable points to the attached effect for the entity.create method.
real xP
real yP
real zP
-- The 'P' vector is the current position vector. zP is the sum of the unit's fly
height and the terrain height at point (xP, yP).
real xV
real yV
real zV
-- The 'V' vector is the current velocity vector. Each parameter is divided by FPS,
the amount of frames per second, which is defined as a constant near the top of SEEcore.
'V' is the rate of change of 'P'.
real xA
real yA
real zA
-- The 'A' vector is the current acceleration vector. Usually, only zA is used, for
gravitational acceleration, but xA and yA can also be used for wind and such.
'A' is the rate of change of 'V'.
trigger onEvent
-- This trigger points to most of the methods defined in entityData.
integer restCount
-- 'restCount' is the current amount of tallied rests.
integer ID
-- 'ID' is the entity's position in the entity.ENTITY array. Useful in entity destruction.
boolean wantRemoveUnit
-- If wantRemoveUnit is true, then the entity will be removed after the end of the current timeframe.
real timedLife
-- The time remaining for timedLife. -1.0 if no timedLife is set. -2.0 if an entity has been removed.
entity constrained
-- This is the pointer to the constrained entity, if any. If an entity A is constrained by another entity B,
then B is not constrained by A.
real constraintConstant
-- The spring constant "k" in Hooke's law: F = -k*d. A high constant is like a very strong rope. A small
constant is like a rubber band.
real constraintLength
-- The distance between two constrained entities.
real constraintFriction
-- The friction opposing the force in Hooke's law, of two constrained entities.
static timer T
-- The main timer controlling the function main.
static group COLL
-- The group inside which collision detection is ran.
static integer COUNT
-- The current amount of entities, active or not.
static entity array ENTITY
-- An array containing all entities in a stack.
static integer COUNT_DES
-- The current amount of 'to-be-destroyed' entities.
static entity array ENTITY_DES
-- An array containing the entities to be destroyed in a stack.
static location tloc = Location(0.0, 0.0)
-- A temporary location used for terrain collision and height.
static location tloc2 = Location(0.0, 0.0)
-- A temporary location used for terrain collision and height. Method List: *******************************************
****** ~ SEE Method List ~ ******
*******************************************
-----------------------------------------------------
*** 1. 'entityData' struct methods:
-----------------------------------------------------
All of the methods below will work with natives corresponding
to the concerned event. For example, GetTriggerPlayer() will
return the selecting player, for the onSelect method.
method onGround takes entity e returns nothing defaults nothing
-- The method that will be run when an entity collides with the ground.
method onCollision takes entity e, entity e2 returns nothing defaults nothing
-- The method that will be run when two entities collide.
method onRest takes entity e returns nothing defaults nothing
-- This method will be run when an entity reaches a resting state.
method onSelect takes entity e returns nothing defaults nothing
-- The onSelect method will run when a player selects an entity.
NOTE : onSelect and onDeselect are delayed, due to Warcraft III's engine.
method onDeselect takes entity e returns nothing defaults nothing
-- The onDeselect method will run when a player deselects an entity.
NOTE : onSelect and onDeselect are delayed, due to Warcraft III's engine.
method onDeath takes entity e returns nothing defaults nothing
-- The onDeath method will run when an entity dies of any cause including
timed life by <entity>.applyTimedLife.
method onExit takes entity e returns nothing defaults nothing
-- onExit will run once an entity exits map bounds.
method onPointOrder takes entity e returns nothing defaults nothing
-- onPointOrder will run when an entity is issued an order to a point.
NOTE : A Warcraft III bug/stupidity makes it such that PauseUnit registers as a point order.
method onTargetOrder takes entity e returns nothing defaults nothing
-- onTargetOrder will be run when an entity is issued an order to a target.
method onOrder takes entity e returns nothing defaults nothing
-- onOrder will run when an entity is issued an immediate order.
method onCreate takes entity e returns nothing defaults nothing
-- onCreate will run when an entity is created.
method onLoop takes entity e returns nothing defaults nothing
-- onLoop will run every iteration of the main function.
-----------------------------------------------------
*** 2. 'entity' struct methods:
-----------------------------------------------------
static method create takes entityData data, player owner, real xPos, real yPos, real height, real facing returns entity
-- Creates an entity with all of the chosen parameters as data.
static method createFromUnit takes entityData data, unit someUnit returns entity
-- Turns someUnit into an entity.
static method createAsUnit takes entityData data, player owner, integer unitID, real xPos, real yPos, real height, real facing returns entity
-- Creates a unit of type unitID at the specified flying height and turns it into an entity.
static method createFromGroup takes integer entityType, group whichGroup returns nothing
-- Takes all units from the group whichGroup and turns them into entities of type entityType. entityType
is a struct's typeid. For example, if a certain entity type is called "Boulder", then this function could
be called in the following manner:
i.e. call entity.createFromGroup(Boulder.typeid, yourGroup)
static method createFromUnitType takes integer entityType, integer unitID, boolexpr filter returns nothing
-- Takes all units on the map of type unitID and obeying to filter and turns them into entities of type entityType.
entityType is a struct's typeid. For example, if a certain entity type is called "Boulder", and you would want to
make a Boulder entity for every human peasant on the map, then the following should be called:
i.e. call entity.createFromUnitType(Boulder.typeid, 'hpea', Filter(function True))
Where Filter(function True) is a filter that always returns true.
method remove takes boolean wantRemoveUnit returns nothing
-- Removes the concerned entity. If wantRemoveUnit is true, then the entity
dummy and effect will be removed too.
method applyTimedLife takes real time, boolean removeUnit returns nothing
-- Applies a timed life to the concerned entity. Is precise up to 1.0/FPS seconds.
method addGravity takes real factor returns nothing
-- Adds gravity's acceleration to the 'A' vector in the entity struct. 'factor'
is a factor to the gravitational acceleration. If it is 1.0, then nothing is changed.
If it is 2.0, then gravity's acceleration is doubled.
method setVelocity takes real xVel, real yVel, real zVel returns nothing
-- Sets the current velocity vector to the vector parameter.
method addVelocity takes real x, real y, real z returns nothing
-- Adds the vector parameter to the current velocity vector.
method setAcceleration takes real xAcc, real yAcc, real zAcc returns nothing
-- Sets the current acceleration vector to the vector parameter.
method addAcceleration takes real x, real y, real z returns nothing
-- Adds the vector parameter to the current acceleration vector.
method setEntityFlyHeight takes real height returns nothing
-- Sets the concerned entity's fly height to the given value. This acts in the same manner as SetUnitFlyHeight.
method removeHealthBar takes nothing returns nothing
-- This method removes the floating health bar above an entity. Useful sometimes for collideable entities created
with createFromUnit or createAsUnit.
method projectWithAngle takes real xyAngle, real zAngle, real speed returns nothing
-- Shoots the concerned entity towards xyAngle and zAngle, with the chosen speed.
method projectToPointTimed takes real xPos, real yPos, real zPos, real time returns nothing
-- Shoots the concerned entity to the target point in a parabolic trajectory,
taking in account the time until ground contact.
NOTE : This DOES take in account gravity's acceleration.
method projectToPointTimedEx takes real xPos, real yPos, real zPos, real time, real maxDist returns nothing
-- This method behaves in the same manner as projectToPointTimed. The difference is that
projectToPointTimedEx has a maxDist parameter which limits the distance to the target point.
If the target point is located further from the entity's current position than maxDist, then
the target point will be scaled to fit maxDist.
NOTE : This DOES take in account gravity's acceleration.
method projectToPointSpeed takes real xPos, real yPos, real zPos, real speed, boolean lob returns nothing
-- Shoots the concerned entity to the position parameter with the chosen speed.
If lob is true, then the entity will be projected to the target point with optimal angle of elevation
to make a lob. If it is false, then it will be projected to the point with optimal angle of elevation
to make a direct hit. If the entity has no gravity, then lob is a useless parameter and the entity will
be projected in a straight line (in such a case, it's possible to use projectTowardsPointSpeed instead)
NOTE : This DOES take in account gravity's acceleration.
method projectToPointSpeedEx takes real xPos, real yPos, real zPos, real speed, boolean lob, real maxDist returns nothing
-- This method behaves in the same manner as projectToPointSpeed. The difference is that
projectToPointSpeedEx has a maxDist parameter which limits the distance to the target point.
If the target point is located further from the entity's current position than maxDist, then
the target point will be scaled to fit maxDist.
NOTE : This DOES take in account gravity's acceleration.
method projectTowardsPointSpeed takes real xPos, real yPos, real zPos, real speed returns nothing
-- This method projects an entity towards the target point. If the entity has acceleration,
then it cannot his the target point, as its velocity will stoop while it is on trajectory. If the
entity does not have acceleration, then it will hit the target point successfully.
NOTE : This does NOT take in account gravity's acceleration.
method addSpringConstraint takes entity attached, real springConstant, real springLength, real friction returns nothing
-- Constrains two entities as a spring constraint, with Hooke's law : F = -k*d. springConstant is the
spring constant in Hooke's law. springLength is the maximum distance between the two entities. friction
is the force opposing the resulting force of Hooke's law.
method removeSpringConstraint takes nothing returns nothing
-- Removes the currently attached entity from the constraint.
method enumEntitiesInRange takes group g, real x, real y, real z, real range, boolexpr filter returns nothing
-- Adds all entities within a given (spherical) range from the chosen point to the group g.
method enumEntitiesInRect takes group g, rect whichRect, boolexpr filter returns nothing
-- Adds all entities within a given rect to the group g.
method enumEntitiesOfType takes group g, integer entityType, boolexpr filter returns nothing
-- Adds all entities of type entityType to the group g. entityType is found using the 'typeid' member.
For example, for a certain entityData struct called "A", the typeid of "A" would be "A.typeid".
Calling the function to get all entities of type "A" would look like the following:
i.e. call entity.enumEntitiesOfType(yourGroup, A.typeid, yourFilter)
method enumEntitiesOfPlayer takes group g, player whichPlayer, boolexpr filter returns nothing
-- Adds all entities owned by a certain player to the group g.
******************************** Finally, an example on how to use this system (A MUCH more elaborate example is included in the attached map) : Example:scope Initialization private function Actions takes nothing returns nothing local entity chain local entity chain2 local integer i = 1 local location templ = Location(0.0, 0.0) // The lines below create the boulder entity, set it's height, and other various data. set chain = entity.createFromUnit( chainBall.create(), CreateUnit(GetLocalPlayer(), 'e001', -360.0, 0.0, 0.0) ) set chain.zP = 1000.0 + GetLocationZ(templ) call SetUnitTimeScale( chain.dummy, 0.0) call SetUnitScale( chain.dummy, 1.5, 1.5, 1.5) call chain.addGravity(1.0) call BJDebugMsg("|cffffcc00Simple Entity Engine 2.0|r by HINDYhat\n\nTarget a point with your steam tank to fire a rocket! The boulder is collideable!") loop exitwhen i > 18 // Creates a chain link entity. set chain2 = entity.create( chainLink.create(), GetLocalPlayer(), 20.0*i - 360, 0.0, 1000.0, 180.0) call chain2.addGravity(1.0) // Creates a constraint between the previously created entity and the new one. call chain.addSpringConstraint( chain2, 100000.0, 20.0, 15000.0) set chain = chain2 set i = i + 1 endloop // Makes the topmost chain link non-affected by SEE. This means that it cannot move. set chain.data.affected = false // Makes the topmost chain link face downwards. call SetUnitAnimationByIndex(chain.dummy, 0) call RemoveLocation(templ) set templ = null // Creates the steam tank entity. call entity.createFromUnit( steamTank.create(), CreateUnit(GetLocalPlayer(), 'hmtt', -800.0, 0.0, 0.0) ) endfunction //=========================================================================== public function InitTrig takes nothing returns nothing set gg_trg_Initialization = CreateTrigger() call TriggerAddAction( gg_trg_Initialization, function Actions ) endfunction endscope |
| 12-02-2007, 12:30 AM | #2 |
Please mention the vJass requirement... somewhere which is obvious... |
| 12-02-2007, 12:35 AM | #3 |
Whoops, sorry 'bout that. |
| 12-02-2007, 12:50 AM | #4 |
Well I must say this really looks promising. It is much better coded than before, and what is most important it is modular! Of course we will need to inspect his in detail ![]() |
| 12-02-2007, 01:02 AM | #5 |
Well I'm thinking of replacing all of my use of triggeractions with triggerconditions because of this : http://66.166.149.149/showthread.php?t=93797 Would that be a good idea? |
| 12-02-2007, 02:00 AM | #6 | |
Quote:
One thing that I've seen is that you're creating timers to manage each projectile, IMO, it's a very bad approach, it's like saying that the time framework is different according to the object. Manage ALL your movement with only ONE timer. |
| 12-02-2007, 02:07 AM | #7 |
Yeah, that's only for the projectile extension. I suppose I made that really quickly. I could work harder on it, and make it actually a useful system. As for now, I'm still fixing up collisions... they're really being nasty. |
| 12-03-2007, 01:10 AM | #8 |
Good to see that you use my GetTerrainNormal method... You have a lot of work left to do on collisions... personally I would say that you should stop caring about those since they are impossible to get right unless you copy my system exactly. It's too much work for anyone who's not insane. That's just a fact. Overall, this is definitely a step in the right direction compared to previous engines, and it looks kind of similar to mine as far as overall design and the user-end goes. |
| 12-03-2007, 08:45 AM | #9 |
grim there is something with ur object vs object collision. u use 1 group for each object for preventing double check. but wat about use only 1 global group, and for each object tat u have checked collision, add it to this group. Everything in this group wont be checked with anymore. And after all the loop, just clear this group. |
| 12-03-2007, 09:03 AM | #10 |
That won't work... if A collides with B it doesn't mean C won't collide with A. And it's kind of off topic in this thread too. |
| 12-03-2007, 09:46 AM | #11 | |
Quote:
Strange, I always thought Anitarf made that. |
| 12-03-2007, 12:46 PM | #12 |
Earth-Fury has helped me alot, and gave me the idea of working with two temp locations and inlining the cross product. I've recently gotten collisions to work a little bit nicer. Anitarf made the GetTerrainNormal shiz in his Vector Functions, but grim just inlined it. It's not really a new method, I think, just a faster one. Sorry if I copied your system too much... just that logically, spherical collisions is the way to go, and your system was awesome when I tested it. I guess I was just trying to make something good. |
| 12-03-2007, 05:43 PM | #13 | |
Updated the system. Collision safety shiz now works and can be changed (it's a constant). Here's the changelog for 1.1: Quote:
|
| 12-03-2007, 06:03 PM | #14 |
I looked briefly at your code and noticed that you're using the EVENT_UNIT_SELECTED event. This has been known to be a very late-running event that doesn't fire at the proper time. I don't know what you'll want to do about that, but I thought I should point it out... |
| 12-03-2007, 06:06 PM | #15 |
Well it's to detect when an entity is selected (with the onSelect method). I could add an IsUnitSelected checker in the main loop, but that would be slow... Besides, it's one of the onEvent methods that people would use the least. I don't think pinpoint precision is necessary in this case. But if you really want me to do something about it... meh. |
