What's total war?
I've updated the code finally. This section is on adding character formations. It shows how to set up the code so that it may be expanded to support an unlimited number of different formations, and how to keep your units in formation when moving to new target destinations. I kinda rushed through it, so lemme know if you find any errors.
sync on
sync rate 60
randomize timer()
set display mode 1024,768,32
Global G_formationX
Global G_formationZ
Global G_middleX#
Global G_middleZ#
squareSize# = 40.0
mapX = 32
mapZ = 32
dim map(mapX,mapZ)
terrainX# = mapX * squareSize#
terrainZ# = mapZ * squareSize#
screenwidth2# = screen width() / 2.0
screenheight2# = screen height() / 2.0
scalefactor# = screen height() / 1.2
type basicUnitActions
isMoving as boolean
isAttacking as boolean
isSelected as boolean
endtype
type basicUnitAnimations
idleStart as integer
idleEnd as integer
walkStart as integer
walkEnd as integer
action1Start as integer
action1End as integer
action2Start as integer
action2End as integer
endtype
type basicUnitProperties
name as string
maxLife as integer
armor as integer
range as integer
speed as float
animations as basicUnitAnimations
endtype
dim characterType(2) as basicUnitProperties
#constant SOLDIER = 1
#constant WORKER = 2
#constant TREE = 1
#constant ANIMATE_IDLE = 1
#constant ANIMATE_WALK = 2
#constant ANIMATE_ACTION1 = 3
#constant ANIMATE_ACTION2 = 4
#constant FORMATION_BLOCK = 1
characterType(SOLDIER).name = "Grunt"
characterType(SOLDIER).maxLife = 80
characterType(SOLDIER).armor = 4
characterType(SOLDIER).range = 0
characterType(SOLDIER).speed = 3.5
characterType(WORKER).name = "Peasant"
characterType(WORKER).maxLife = 45
characterType(WORKER).armor = 0
characterType(WORKER).range = 0
characterType(WORKER).speed = 2.25
characterType(WORKER).animations.idleStart = 326
characterType(WORKER).animations.idleEnd = 359
characterType(WORKER).animations.walkStart = 1
characterType(WORKER).animations.walkEnd = 13
characterType(WORKER).animations.action1Start = 111
characterType(WORKER).animations.action1End = 125
type character
object as integer
action as basicUnitActions
isHarvesting as integer
targetX as float
targetZ as float
group as integer
unit as integer
life as integer
resourceQuantity as integer
lastAction as integer
stage as integer
formation as integer
endtype
dim characters(0) as character
type resource
object as integer
quantityLeft as integer
resourceType as integer
endtype
dim resources(0) as resource
remstart
rem create 5 soldiers
for t = 10 to 14
make object cube t, 10
array insert at bottom characters(0)
index = array count(characters(0))
characters(index).object = t
characters(index).action.isMoving = 0
characters(index).action.isAttacking = 0
characters(index).action.isSelected = 0
characters(index).isHarvesting = 0
characters(index).group = -1
characters(index).unit = SOLDIER
characters(index).life = characterType(SOLDIER).maxLife
position object t, rnd(1000),0,rnd(100)
next t
remend
rem create 4 workers
for t = 10 to 18
load object "models\dwarf1.x", t
scale object t, 1000, 1000, 1000
set object ambient t, 0
set object speed t, 30
loop object t, 327, 360
array insert at bottom characters(0)
index = array count(characters(0))
characters(index).object = t
characters(index).action.isMoving = 0
characters(index).action.isAttacking = 0
characters(index).action.isSelected = 0
characters(index).isHarvesting = 0
characters(index).group = -1
characters(index).unit = WORKER
characters(index).life = characterType(WORKER).maxLife
characters(index).stage = ANIMATE_IDLE
position object t, rnd(1000),0,rnd(100)
next t
rem camera x & y movement speed
cspd# as float = 6.0
rem camera height movement speed
cyspd# as float = 4.0
rem starting camera height position
cy# as float = 350.0
rem make our terrain
make matrix 1, terrainX#, terrainZ#, 10, 10
load image "images\grass.bmp", 1
prepare matrix texture 1, 1, 1, 1
rem make 10 trees
for t = 20 to 29
load object "models\tree.x", t
set object ambient t, 0
yrotate object t, rnd(360)
x = rnd(mapX)
z = rnd(mapZ)
rem position the tree inside the center of grid square (x,z)
position object t, x*squareSize#-(squareSize#/2), 0, z*squareSize#-(squareSize#/2)
rem update map array so it knows a tree is at (8,3)
map(x,z) = TREE
rem update resource array list
array insert at bottom resources(0)
index = array count(resources(0))
resources(index).object = t
rem give resource a quantity between 100 to 200
resources(index).quantityLeft = rnd(100)+100
resources(index).resourceType = TREE
next t
REM ============= load sounds ==============
load sound "sounds\Hit tree.wav", 1
REM ========== Main loop ===================
DO
rem camera controls
gosub _Control_Camera
rem define a variable to hold mouse click status so only 1 system call is made each loop
_mouse_click = mouseclick()
rem get scancode of key pressed
_code = scancode()
rem reset variable
_resource = 0
_selected_count = 0
_formation = 0
if spacekey()=1 then _formation = FORMATION_BLOCK : getMeetingCoordinates()
rem if user right-clicks
if _mouse_click = 2 and mouseFlag = 0
mouseFlag = 1
gosub _Click_Matrix_Position
_resource = pickResource()
rem a new target position has been selected
rem switch flag on to let selected characters know they need to update their target position
assignTargetPositionFlag = 1
endif
rem if user has is not right-clicking, switch off mouseFlag(flag prevents multiple clicks in a row)
if _mouse_click <> 2 then mouseFlag = 0
rem if user left-clicks for first time, switch selectionFlag
if _mouse_click = 1 and selectionFlag = 0
selectionFlag = 1
bx = mouseX()
by = mouseY()
endif
rem if user left-clicks and has started a selection box
if _mouse_click = 1 and selectionFlag = 1
bx2 = mouseX()
by2 = mouseY()
endif
rem if user has stopped drawing a selection box, switch off selectionFlag
if _mouse_click <> 1 then selectionFlag = 0
rem if user is currently drawing a selection box
if selectionFlag = 1
rem sort the coordinates
gosub _sort_selection_coordinates
rem draw the box
drawSelectionBox(selectionX1, selectionY1, selectionX2, selectionY2)
endif
rem group assignment shortcut key listener
gosub _check_for_group_assignment
rem group selection shortcut key listener
gosub _check_for_group_selection
rem loop through all characters
for char = 1 to array count(characters(0))
rem reset variable to default animation stage
stage = ANIMATE_IDLE
rem get object number of this character for future object related actions
obj = characters(char).object
rem if user is currently drawing a selection box
if selectionFlag = 1
rem make sure unit is visibly on the screen
if object in screen(obj) = 1
rem get the character's 2D screen coordinates
cx = object screen x(obj)
cy = object screen y(obj)
rem if character is within the selection box, update isSelected variable
if cx > selectionX1 and cx < selectionX2 and cy > selectionY1 and cy < selectionY2
characters(char).action.isSelected = 1
else
characters(char).action.isSelected = 0
endif
endif
else
rem if user is trying to get a previously assigned group
if getGroupFlag = 1
rem check to see if this character is part of the group we want
rem if not, deselect it incase it's currently selected
if characters(char).group = _group_number
characters(char).action.isSelected = 1
else
characters(char).action.isSelected = 0
endif
endif
endif
rem if this character is selected
if characters(char).action.isSelected = 1
rem keep count of how many units are currently selected
inc _selected_count, 1
if _formation > 0
rem assign formation to character
characters(char).formation = _formation
rem call the construction of the formation
getFormation(_selected_count, _formation)
rem throw flag so that character gets assigned its new target coordinates
assignTargetPositionFlag = 1
rem assign new target coordinates for this character
targetX# = G_middleX#
targetZ# = G_middleZ#
rem reset previous orders
characters(char).isHarvesting = 0
characters(char).action.isAttacking = 0
endif
_selected_unit = char
rem if player has selected a resource
if _resource > 0
rem if this selected character is a worker
if characters(char).unit = WORKER
rem assign this character the resource
characters(char).isHarvesting = _resource
endif
endif
if assignGroupFlag = 1
characters(char).group = _group_number
endif
rem displays the group number of the character, if one has been assigned
if characters(char).group > -1 then text object screen x(obj), object screen y(obj), str$(characters(char).group)
rem if the user has right-clicked during this game loop..
if assignTargetPositionFlag = 1
rem tell character to move
characters(char).action.isMoving = 1
rem check to see if character belongs to a formation
offsetX# = 0
offsetZ# = 0
if characters(char).formation > 0
getFormation(_selected_count, characters(char).formation)
offsetX# = G_formationX*squareSize#
offsetZ# = G_formationZ*squareSize#
endif
rem assign the target coordinates to character
characters(char).targetX = targetX#+offsetX#
characters(char).targetZ = targetZ#+offsetZ#
rem rotate character to point towards target
angle# = atanfull((characters(char).targetX - object position x(obj)), (characters(char).targetZ - object position z(obj)))
yrotate object obj, angle#
rem if character was told to move someplace else, and a resource was not targeted, unassigned its resource target (if it had any)
if _resource = 0 then characters(char).isHarvesting = 0
endif
else
rem if character is not selected and user is assigning a group number,
rem then reset this character's unit value to -1(no group) if it belonged to the current group assignment
if assignGroupFlag = 1
if characters(char).group = _group_number then characters(char).group = -1
endif
endif
rem if character is moving and hasn't reached its destination yet
if characters(char).action.isMoving = 1
stage = ANIMATE_WALK
rem get current angle of character
angle# = object angle y(obj)
rem get speed of character
speed# = characterType(characters(char).unit).speed
rem determine new position of character
x# = newxvalue(object position x(obj), angle#, speed#)
z# = newzvalue(object position z(obj), angle#, speed#)
y# = get ground height(1,x#,z#)
rem position character at its new coordinates
position object obj, x#, y#, z#
rem get the target of this character
tx# = characters(char).targetX
tz# = characters(char).targetZ
rem if not moving towards a resource for harvesting
if characters(char).isHarvesting = 0
rem if character is within range of target position, then stop moving
if getSquaredDistance#(x#, z#, tx#, tz#) < (speed# * speed#)
characters(char).action.isMoving = 0
endif
else
rem else if character is moving towards a resource for harvesting
resObj = resources(characters(char).isHarvesting).object
if getSquaredDistance#(object position x(obj), object position z(obj),object position x(resObj), object position z(resObj)) < (squareSize#*squareSize#)
characters(char).action.isMoving = 0
endif
endif
endif
rem if character has been assigned a resource
resID = characters(char).isHarvesting
if resID > 0
if resources(resID).quantityLeft > 0
rem get object number of resource
res = resources(resID).object
rem if character is within range of resource (within 40 units)
if getSquaredDistance#(object position x(obj), object position z(obj),object position x(res), object position z(res)) < (squareSize#*squareSize#)
rem wood chopping animation
stage = ANIMATE_ACTION1
rem make sure character hasn't already gathered from this resource within the last 0.75 second
if characters(char).lastAction+750 < timer()
play sound 1
rem increase character's gathered resource quantity
characters(char).resourceQuantity = characters(char).resourceQuantity + 1
rem reset character's time of last action it did (harvested)
characters(char).lastAction = timer()
rem decrease quantity of resource
resources(resID).quantityLeft = resources(resID).quantityLeft - 1
rem if resource object has no more resources
if resources(resID).quantityLeft <= 0
characters(char).isHarvesting = 0
endif
endif
endif
endif
endif
rem set object's animation to match its current action
setAnimation(char,stage)
next char
rem positions from new target have been assigned, switch flag off
assignTargetPositionFlag = 0
set cursor 0,0
rem if only 1 unit is selected, then display its properties
if _selected_Count = 1
print characterType(characters(_selected_unit).unit).name
print "Health: ",characters(_selected_unit).life,"/",characterType(characters(_selected_unit).unit).maxLife
print "Armor: ", characterType(characters(_selected_unit).unit).armor
print "Gathered: ",characters(_selected_unit).resourceQuantity
endif
sync
LOOP
rem controls camera movement
_Control_Camera:
if upkey() then inc cz#,cspd#
if downkey() then dec cz#,cspd#
if rightkey() then inc cx#,cspd#
if leftkey() then dec cx#,cspd#
if scancode()=30 then inc cy#,cyspd#
if scancode()=44 then dec cy#,cyspd#
if cy#>400 then cy#=400
if cy#<200 then cy#=200
offsetx# = cx#
offsety# = 0
offsetz# = cz#+300
position camera cx#,cy#,cz#
point camera offsetx#,offsety#,offsetz#
RETURN
rem sorts the coordinates of selection box so that (X1,y1)
rem is smaller than (X2,Y2)
_sort_selection_coordinates:
if bx < bx2
selectionX1 = bx
selectionX2 = bx2
else
selectionX1 = bx2
selectionX2 = bx
endif
if by < by2
selectionY1 = by
selectionY2 = by2
else
selectionY1 = by2
selectionY2 = by
endif
RETURN
rem translates 2D mouse coordinates into 3D world coordinates
_Click_Matrix_Position:
mouseposx#=mousex() - screenwidth2#
mouseposy#=screenheight2# - mousey()
dist#=sqrt((camera position x()-offsetx#)^2+(camera position y()-offsety#)^2+(camera position z()-offsetz#)^2)
if mouseposy#<>0
vectorang#=atanfull(scalefactor#,mouseposy#)
else
vectorang#=90.0
endif
if vectorang#+camera angle x()>90.965
ratio#=mouseposy#/(sin((vectorang#+camera angle x() )-90.0))
cursorposy#=ratio#*sin(180.0-vectorang#)
else
cursorposy#=1000000.0
endif
hyplength#=scalefactor#/sin(vectorang#)
cursorposx#=(((ratio#*sin(90.0-camera angle x() ))/hyplength#)+1.0)*mouseposx#
if mouseposx#<>0
angtotal#=wrapvalue(camera angle y()-atanfull(mouseposy#,mouseposx#))
else
if mouseposy#<0
angtotal#=wrapvalue(camera angle y()+90.0)
else
angtotal#=wrapvalue(camera angle y()-90.0)
endif
endif
cameraang#=wrapvalue(360.0-camera angle y() )
cosang#=cos(cameraang#)
sinang#=sin(cameraang#)
movex# = (dist#/scalefactor#)*((cosang#*cursorposx#)+((-1*sinang#)*cursorposy#))
movez# = (dist#/scalefactor#)*((sinang#*cursorposx#)+(cosang#*cursorposy#))
rem 3D position stored into these variables
targetX#=movex#+offsetx#
targetZ#=movez#+offsetz#
movey#=0
RETURN
rem listens for user to press the shortcut for assigning character group numbers
_check_for_group_assignment:
rem reset flag
assignGroupFlag = 0
rem if user pressed control key, then check scancode of other key they pressed
if controlkey() = 1
select _code
case 2 : _group_number = 1 : endcase
case 3 : _group_number = 2 : endcase
case 4 : _group_number = 3 : endcase
case 5 : _group_number = 4 : endcase
case 6 : _group_number = 5 : endcase
case 7 : _group_number = 6 : endcase
case 8 : _group_number = 7 : endcase
case 9 : _group_number = 8 : endcase
case 10 : _group_number = 9 : endcase
case default : _group_number = -1 : endcase
endselect
rem switch on flag so the character loop knows to assign a group
if _group_number > -1 then assignGroupFlag = 1
endif
RETURN
rem checks to see user has pressed a key referring to a group number
_check_for_group_selection:
rem reset flag
getGroupFlag = 0
rem make sure user isn't pressing control key and trying to actually assign groups rather than select
if controlkey() = 0
select _code
case 2 : _group_number = 1 : endcase
case 3 : _group_number = 2 : endcase
case 4 : _group_number = 3 : endcase
case 5 : _group_number = 4 : endcase
case 6 : _group_number = 5 : endcase
case 7 : _group_number = 6 : endcase
case 8 : _group_number = 7 : endcase
case 9 : _group_number = 8 : endcase
case 10 : _group_number = 9 : endcase
case default : _group_number = -1 : endcase
endselect
rem switch on flag so character loops knows to select that group
if _group_number > -1 then getGroupFlag = 1
endif
RETURN
rem draws a box with top left corner at (x1,y1)
rem and bottom right corner at (x2,y2)
function drawSelectionBox(x1 as integer, y1 as integer, x2 as integer, y2 as integer)
line x1, y1, x2, y1
line x1, y2, x2, y2
line x1, y1, x1, y2
line x2, y1, x2, y2
endfunction
rem Given the character's number in the formation
rem function stores the column and row into the global
rem variables G_formationX and G_formationZ
function blockFormation(n as integer)
dec n, 1
ring = sqrt(n)
idx = n - (ring*ring)
if idx >= ring
G_formationZ = ring
G_formationX = 2*ring - idx
else
G_formationX = ring
G_formationZ = idx
endif
endfunction
rem calls the proper function for character formation
function getFormation(unit as integer, formation as integer)
if formation = FORMATION_BLOCK then blockFormation(unit)
endfunction
rem finds the coordinates in the middle of all selected units.
function getMeetingCoordinates()
lowX#=999999
lowZ#=999999
highX#=0
highZ#=0
rem loop through all characters
for char = 1 to array count(characters(0))
if characters(char).action.isSelected = 1
obj = characters(char).object
if object position x(obj) < lowX# then lowX# = object position x(obj)
if object position z(obj) < lowZ# then lowZ# = object position z(obj)
if object position x(obj) > highX# then highX# = object position x(obj)
if object position z(obj) < highZ# then highZ# = object position z(obj)
endif
next char
rem assign coordinates to the global variables
G_middleX# = lowX# + (highX#-lowX#)/2.0
G_middleZ# = lowZ# + (highZ#-lowZ#)/2.0
endfunction
rem returns the squared distance between two points
function getSquaredDistance#(x1 as float, y1 as float, x2 as float, y2 as float)
d# = (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)
endfunction d#
rem returns the index to the resource the mouse is on
function pickResource()
for t = 1 to array count(resources(0))
obj = resources(t).object
check = pick object(mousex(), mousey(),obj,obj)
if check > 0
exitfunction t
endif
next t
endfunction 0
rem set object's animation loop, if it has changed states
function setAnimation(char as integer, stage as integer)
rem if object's animation is already set for this stage, then exit
if characters(char).stage = stage then exitfunction
unit = characters(char).unit
if stage = ANIMATE_IDLE
loop object characters(char).object,characterType(unit).animations.idleStart, characterType(unit).animations.idleEnd
characters(char).stage = stage
endif
if stage = ANIMATE_WALK
loop object characters(char).object,characterType(unit).animations.walkStart, characterType(unit).animations.walkEnd
characters(char).stage = stage
endif
if stage = ANIMATE_ACTION1
loop object characters(char).object,characterType(unit).animations.action1Start, characterType(unit).animations.action1End
characters(char).stage = stage
endif
if stage = ANIMATE_ACTION2
loop object characters(char).object,characterType(unit).animations.action2Start, characterType(unit).animations.action2End
characters(char).stage = stage
endif
endfunction
"eureka" - Archimedes