05-03-2008, 01:26 PM
Pushable Blocks (originally requested of me by Gameboy)
**********************************************************
Difficulty (1/5 for copy and paste) - (3/5 to understand it!)
Client side code changes
First of all we'll set up the variables we are going to use to handle our pushblocks.
It's based the door tile system already in mirage, so you may notice a few similarities.
modGlobals - Add these new variable declarations.
modConstants - Just an extra TILE_TYPE constant here.
modTypes - Add the TypeRec that holds the info about our pushblocks.
Next We'll need to add the new editor sections to help us define how each pushblock will move.
frmMirage:
Add an option button called 'optPushBlock' to the Attributes frame.
Then add the following code to the form near the other Attribute orientated Subs.
frmPushBlock: A new form
Make frmPushBlock.
Create three frames - fraDir1 / fraDir2 / fraDir3
With the captions set to - Direction 1 / Direction 2 / Direction 3
Add five option buttons to each frame named - optDir#None / optDir#Up / optDir#Down / optDir#Left / optDir#Right
- where # is the number of frame.
Set the Value of - optDir1None / optDir2None / optDir3None to True.
And of course, create the OK and Cancel buttons - cmdPushBlockOK / cmdPushBlockCancel
The extra code and new subs in modGameLogic prepare the map, blit the pushblocks in the game loop and deal with how a pushblock should move when pushed.
modGameLogic
*Sub Main - Add along with the ClearTempTile call.
*Sub GameLoop - Add underneath the For loop that Calls BltTile
The next one is quite self explanatory.
If your not sure, check a little further down in the game loop for a block of code commented
' Blit out attribs if in editor.
Add this next piece of code underneath the code block commented
' Process player movements (actually move them)
*Sub BltTile - You'll need to change this Sub slightly to add in the pushblocks,
directly after the ' Is there an animation tile to plot? comment change the line of code to this.
These next two Subs are new, I put the first one in after Sub BltTile,
and the second after Sub ProcessMovement.
*Sub EditorMouseDown - It should be fairly easy to see where this code slots into the Sub.
*Sub CanMove - Pay attention here, or you'll get it wrong.
This piece of code needs to go into each of the directions mentioned in Sub CanMove.
I've put mine in directly after the block of code commented ' Check to see if the key door is open or not.
Remember, once for each direction, eg, If DirUp then, etc.
The Sub to clear the pushblocks can be added in with the other clearing subs.
The last addition deals with the packet the client receives to tell it to move a pushblock.
A Blocked attribute needs to be added if it is moving or an Npc_Avoid if not.
modHandledata - Add a new variable declaration at the top of the sub.
Then add this in the sub along with the other packet descriptions.
I've added mine in just after the Map Key Packet.
Pushable Blocks - Server side changes
*******************************************
Once again we need to set up the variables for our new pushblocks.
modGlobals
modConstants
modTypes
modGeneral - As the comment in the code says, this closes any open pushblocks from a timer.
*Sub GameAI - Add underneath section of code used for closing doors
modGameLogic
*Sub PlayerMove - Add after the block of code commented ' Check for key trigger open
Lastly we need to add a function to check if there is someone on the other side of the pushblock the player wants to push stopping them doing so, I added this with the other Functions at the top of modGameLogic.
To use the pushblocks in your game, enter the editor, place a MASK layer tile where you want the block.
Select the Pushblock option from the attributes section of the map editor and select the directions it can move.
Click on the map where you placed the MASK tile, et voila, a working pushblock.
There is a slight issue I noticed while running the server and client of a much larger game than Mirage on the same PC.
At times of lag a player could push a block and move over it but the client didn't move the block over.
Chances are it won't happen too often with a dedicated server, or a little twiddling with the code should sort it out.
Enjoy.
**********************************************************
Difficulty (1/5 for copy and paste) - (3/5 to understand it!)
Client side code changes
First of all we'll set up the variables we are going to use to handle our pushblocks.
It's based the door tile system already in mirage, so you may notice a few similarities.
modGlobals - Add these new variable declarations.
Code:
' Used for map PushBlock editor
Public PushDir1 As Byte
Public PushDir2 As Byte
Public PushDir3 As Byte
Code:
Public PushTile(0 To MAX_MAPX, 0 To MAX_MAPY) As PushTileRec
Code:
Public Const TILE_TYPE_PUSHBLOCK = 7
Code:
Type PushTileRec
Pushed As Byte
Dir As Byte
Moving As Byte
XOffset As Integer
YOffset As Integer
End Type
frmMirage:
Add an option button called 'optPushBlock' to the Attributes frame.
Then add the following code to the form near the other Attribute orientated Subs.
Code:
Private Sub optPushBlock_Click()
frmPushBlock.Show vbModal
End Sub
Make frmPushBlock.
Create three frames - fraDir1 / fraDir2 / fraDir3
With the captions set to - Direction 1 / Direction 2 / Direction 3
Add five option buttons to each frame named - optDir#None / optDir#Up / optDir#Down / optDir#Left / optDir#Right
- where # is the number of frame.
Set the Value of - optDir1None / optDir2None / optDir3None to True.
And of course, create the OK and Cancel buttons - cmdPushBlockOK / cmdPushBlockCancel
Code:
Option Explicit
Private Sub cmdPushBlockOK_Click()
If optDir1None.Value = True Then
PushDir1 = 0
ElseIf optDir1Up.Value = True Then
PushDir1 = 1
ElseIf optDir1Down.Value = True Then
PushDir1 = 2
ElseIf optDir1Left.Value = True Then
PushDir1 = 3
ElseIf optDir1Right.Value = True Then
PushDir1 = 4
Else
PushDir1 = 0
End If
If optDir2None.Value = True Then
PushDir2 = 0
ElseIf optDir2Up.Value = True Then
PushDir2 = 1
ElseIf optDir2Down.Value = True Then
PushDir2 = 2
ElseIf optDir2Left.Value = True Then
PushDir2 = 3
ElseIf optDir2Right.Value = True Then
PushDir2 = 4
Else
PushDir2 = 0
End If
If optDir3None.Value = True Then
PushDir3 = 0
ElseIf optDir3Up.Value = True Then
PushDir3 = 1
ElseIf optDir3Down.Value = True Then
PushDir3 = 2
ElseIf optDir3Left.Value = True Then
PushDir3 = 3
ElseIf optDir3Right.Value = True Then
PushDir3 = 4
Else
PushDir3 = 0
End If
Unload Me
End Sub
Private Sub cmdPushBlockCancel_Click()
Unload Me
End Sub
modGameLogic
*Sub Main - Add along with the ClearTempTile call.
Code:
Call ClearPushTile
Code:
' Blit out pushblock movement
For y = 0 To MAX_MAPY
For x = 0 To MAX_MAPX
If PushTile(x, y).Moving > 0 Then
Call BltPushBlock(x, y)
End If
Next x
Next y
If your not sure, check a little further down in the game loop for a block of code commented
' Blit out attribs if in editor.
Code:
If .Type = TILE_TYPE_PUSHBLOCK Then Call DrawText(TexthDC, x * PIC_X + 8, y * PIC_Y + 8, "P", QBColor(White))
' Process player movements (actually move them)
Code:
' Process pushblock movements (actually move them)
For y = 0 To MAX_MAPY
For x = 0 To MAX_MAPX
If PushTile(x, y).Moving > 0 Then
Call ProcessPushBlock(x, y)
End If
Next x
Next y
directly after the ' Is there an animation tile to plot? comment change the line of code to this.
Code:
If Anim1 > 0 And TempTile(x, y).DoorOpen = NO And PushTile(x, y).Pushed = NO And PushTile(x, y).Moving = 0 Then
and the second after Sub ProcessMovement.
Code:
Public Sub BltPushBlock(ByVal x As Integer, ByVal y As Integer)
Dim x1 As Long, y1 As Long
' Only used if ever want to switch to blt rather then bltfast
'With rec_pos
'.top = GetPlayerY(Index) * PIC_Y + Player(Index).YOffset
'.Bottom = .top + PIC_Y
'.Left = GetPlayerX(Index) * PIC_X + Player(Index).XOffset
'.Right = .Left + PIC_X
'End With
With rec
.top = Int(Map.Tile(x, y).Mask / 7) * PIC_Y
.Bottom = .top + PIC_Y
.Left = (Map.Tile(x, y).Mask - Int(Map.Tile(x, y).Mask / 7) * 7) * PIC_X
.Right = .Left + PIC_X
End With
x1 = x * PIC_X + PushTile(x, y).XOffset
y1 = y * PIC_Y + PushTile(x, y).YOffset
' Check if its out of bounds because of the offset
If y1 < 0 Then
y1 = 0
With rec
.top = .top + (y1 * -1)
End With
End If
Call DD_BackBuffer.BltFast(x1, y1, DD_TileSurf, rec, DDBLTFAST_WAIT Or DDBLTFAST_SRCCOLORKEY)
End Sub
Code:
Sub ProcessPushBlock(ByVal x As Integer, ByVal y As Integer)
' Check if player is walking, and if so process moving the pushblock over
If PushTile(x, y).Moving = MOVING_WALKING Then
If PushTile(x, y).Pushed = YES Then
Select Case PushTile(x, y).Dir
Case DIR_UP
PushTile(x, y).YOffset = PushTile(x, y).YOffset - WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = -32) Then
PushTile(x, y).Moving = 0
Map.Tile(x, y - 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_DOWN
PushTile(x, y).YOffset = PushTile(x, y).YOffset + WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 32) Then
PushTile(x, y).Moving = 0
Map.Tile(x, y + 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_LEFT
PushTile(x, y).XOffset = PushTile(x, y).XOffset - WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = -32) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
Map.Tile(x - 1, y).Mask = Map.Tile(x, y).Mask
End If
Case DIR_RIGHT
PushTile(x, y).XOffset = PushTile(x, y).XOffset + WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 32) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
Map.Tile(x + 1, y).Mask = Map.Tile(x, y).Mask
End If
End Select
Else
Select Case PushTile(x, y).Dir
Case DIR_UP
PushTile(x, y).YOffset = PushTile(x, y).YOffset + WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
'Map.Tile(x, y - 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_DOWN
PushTile(x, y).YOffset = PushTile(x, y).YOffset - WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
'Map.Tile(x, y + 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_LEFT
PushTile(x, y).XOffset = PushTile(x, y).XOffset + WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
'Map.Tile(x - 1, y).Mask = Map.Tile(x, y).Mask
End If
Case DIR_RIGHT
PushTile(x, y).XOffset = PushTile(x, y).XOffset - WALK_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
'Map.Tile(x + 1, y).Mask = Map.Tile(x, y).Mask
End If
End Select
End If
End If
' Check if player is running, and if so process moving the pushblock over
If PushTile(x, y).Moving = MOVING_RUNNING Then
Select Case PushTile(x, y).Dir
Case DIR_UP
PushTile(x, y).YOffset = PushTile(x, y).YOffset - RUN_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = -32) Then
PushTile(x, y).Moving = 0
Map.Tile(x, y - 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_DOWN
PushTile(x, y).YOffset = PushTile(x, y).YOffset + RUN_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 0) And (PushTile(x, y).YOffset = 32) Then
PushTile(x, y).Moving = 0
Map.Tile(x, y + 1).Mask = Map.Tile(x, y).Mask
End If
Case DIR_LEFT
PushTile(x, y).XOffset = PushTile(x, y).XOffset - RUN_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = -32) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
Map.Tile(x - 1, y).Mask = Map.Tile(x, y).Mask
End If
Case DIR_RIGHT
PushTile(x, y).XOffset = PushTile(x, y).XOffset + RUN_SPEED
' Check if completed walking over to the next tile
If (PushTile(x, y).XOffset = 32) And (PushTile(x, y).YOffset = 0) Then
PushTile(x, y).Moving = 0
Map.Tile(x + 1, y).Mask = Map.Tile(x, y).Mask
End If
End Select
End If
End Sub
Code:
If frmMirage.optPushBlock.Value = True Then
.Type = TILE_TYPE_PUSHBLOCK
.Data1 = PushDir1
.Data2 = PushDir2
.Data3 = PushDir3
Map.Tile(x1, y1 - 1).Type = TILE_TYPE_NPCAVOID
Map.Tile(x1, y1 + 1).Type = TILE_TYPE_NPCAVOID
Map.Tile(x1 - 1, y1).Type = TILE_TYPE_NPCAVOID
Map.Tile(x1 + 1, y1).Type = TILE_TYPE_NPCAVOID
End If
This piece of code needs to go into each of the directions mentioned in Sub CanMove.
I've put mine in directly after the block of code commented ' Check to see if the key door is open or not.
Remember, once for each direction, eg, If DirUp then, etc.
Code:
' If there's a pushblock there, see if we can push it
If Map.Tile(GetPlayerX(MyIndex), GetPlayerY(MyIndex) - 1).Type = TILE_TYPE_PUSHBLOCK Then
If PushTile(GetPlayerX(MyIndex), GetPlayerY(MyIndex) - 1).Pushed = NO Then
If (Map.Tile(GetPlayerX(MyIndex), GetPlayerY(MyIndex) - 1).Data1 DIR_UP + 1) And (Map.Tile(GetPlayerX(MyIndex), GetPlayerY(MyIndex) - 1).Data2 DIR_UP + 1) And (Map.Tile(GetPlayerX(MyIndex), GetPlayerY(MyIndex) - 1).Data3 DIR_UP + 1) Then
CanMove = False
' Set the new direction if they weren't facing that direction
If d DIR_UP Then
Call SendPlayerDir
End If
Exit Function
End If
End If
End If
Code:
Sub ClearPushTile()
Dim x As Long, y As Long
For y = 0 To MAX_MAPY
For x = 0 To MAX_MAPX
PushTile(x, y).Pushed = 0
Next x
Next y
End Sub
A Blocked attribute needs to be added if it is moving or an Npc_Avoid if not.
modHandledata - Add a new variable declaration at the top of the sub.
Code:
Dim f As Long
I've added mine in just after the Map Key Packet.
Code:
' ::::::::::::::::::::::
' :: PushBlock packet ::
' ::::::::::::::::::::::
If (LCase(Parse(0)) = "pushblock") Then
x = Val(Parse(1)) 'x co-ordinate
y = Val(Parse(2)) 'y co-ordinate
n = Val(Parse(3)) 'Pushed Value
i = Val(Parse(4)) 'Player Direction
f = Val(Parse(5)) 'Movement
PushTile(x, y).Pushed = n
PushTile(x, y).Moving = f
If PushTile(x, y).Pushed = NO Then
Select Case PushTile(x, y).Dir
Case DIR_UP
Map.Tile(x, y - 1).Mask = 0
Map.Tile(x, y - 1).Type = TILE_TYPE_NPCAVOID
PushTile(x, y).XOffset = 0
PushTile(x, y).YOffset = -32
Case DIR_DOWN
Map.Tile(x, y + 1).Mask = 0
Map.Tile(x, y + 1).Type = TILE_TYPE_NPCAVOID
PushTile(x, y).XOffset = 0
PushTile(x, y).YOffset = 32
Case DIR_LEFT
Map.Tile(x - 1, y).Mask = 0
Map.Tile(x - 1, y).Type = TILE_TYPE_NPCAVOID
PushTile(x, y).XOffset = -32
PushTile(x, y).YOffset = 0
Case DIR_RIGHT
Map.Tile(x + 1, y).Mask = 0
Map.Tile(x + 1, y).Type = TILE_TYPE_NPCAVOID
PushTile(x, y).XOffset = 32
PushTile(x, y).YOffset = 0
End Select
Exit Sub
End If
PushTile(x, y).Dir = i
If PushTile(x, y).Pushed = YES Then
Select Case PushTile(x, y).Dir
Case DIR_UP
Map.Tile(x, y - 1).Type = TILE_TYPE_BLOCKED
Case DIR_DOWN
Map.Tile(x, y + 1).Type = TILE_TYPE_BLOCKED
Case DIR_LEFT
Map.Tile(x - 1, y).Type = TILE_TYPE_BLOCKED
Case DIR_RIGHT
Map.Tile(x + 1, y).Type = TILE_TYPE_BLOCKED
End Select
End If
PushTile(x, y).XOffset = 0
PushTile(x, y).YOffset = 0
Exit Sub
End If
Pushable Blocks - Server side changes
*******************************************
Once again we need to set up the variables for our new pushblocks.
modGlobals
Code:
Public PushTile(1 To MAX_MAPS) As PushTileRec
Code:
Public Const TILE_TYPE_PUSHBLOCK = 7
Code:
Type PushTileRec
Pushed(0 To MAX_MAPX, 0 To MAX_MAPY) As Byte
PushedTimer As Long
End Type
*Sub GameAI - Add underneath section of code used for closing doors
Code:
' /////////////////////////////////////////
' // This is used for closing pushblocks //
' /////////////////////////////////////////
If TickCount > PushTile(y).PushedTimer + 5000 Then
For y1 = 0 To MAX_MAPY
For x1 = 0 To MAX_MAPX
If Map(y).Tile(x1, y1).Type = TILE_TYPE_PUSHBLOCK And PushTile(y).Pushed(x1, y1) = YES Then
If PushBlockBlocked(y, x1, y1) = False Then
PushTile(y).Pushed(x1, y1) = NO
Call SendDataToMap(y, "PUSHBLOCK" & SEP_CHAR & x1 & SEP_CHAR & y1 & SEP_CHAR & 0 & SEP_CHAR & 0 & SEP_CHAR & 1 & SEP_CHAR & END_CHAR)
Else
PushTile(y).PushedTimer = GetTickCount
End If
End If
Next x1
Next y1
End If
*Sub PlayerMove - Add after the block of code commented ' Check for key trigger open
Code:
' Check for pushblock trigger
If Map(GetPlayerMap(Index)).Tile(GetPlayerX(Index), GetPlayerY(Index)).Type = TILE_TYPE_PUSHBLOCK Then
x = GetPlayerX(Index)
y = GetPlayerY(Index)
If Map(GetPlayerMap(Index)).Tile(x, y).Type = TILE_TYPE_PUSHBLOCK And PushTile(GetPlayerMap(Index)).Pushed(x, y) = NO Then
PushTile(GetPlayerMap(Index)).Pushed(x, y) = YES
PushTile(GetPlayerMap(Index)).PushedTimer = GetTickCount
Call SendDataToMap(GetPlayerMap(Index), "PUSHBLOCK" & SEP_CHAR & x & SEP_CHAR & y & SEP_CHAR & 1 & SEP_CHAR & GetPlayerDir(Index) & SEP_CHAR & Movement & SEP_CHAR & END_CHAR)
Call MapMsg(GetPlayerMap(Index), "A block is being pushed.", White)
End If
End If
Code:
Function PushBlockBlocked(ByVal MapNum As Long, ByVal x As Integer, ByVal y As Integer) As Boolean
Dim i As Long
PushBlockBlocked = False
For i = 1 To MAX_PLAYERS
If Player(i).Char(Player(i).CharNum).Map = MapNum Then
If Player(i).Char(Player(i).CharNum).x = x And Player(i).Char(Player(i).CharNum).y = y Then
PushBlockBlocked = True
End If
End If
Next i
End Function
Select the Pushblock option from the attributes section of the map editor and select the directions it can move.
Click on the map where you placed the MASK tile, et voila, a working pushblock.
There is a slight issue I noticed while running the server and client of a much larger game than Mirage on the same PC.
At times of lag a player could push a block and move over it but the client didn't move the block over.
Chances are it won't happen too often with a dedicated server, or a little twiddling with the code should sort it out.
Enjoy.