Legacy GM Damage step triggering twice from full health

A

Akari_Enderwolf

Guest
I've put together a health system which acts similar to that of the Metroid games, even being able to have and display extra health tanks.

I used some tutorials for setting up a testing game for it by following the tutorials from Shaun Spalding.

I also set up a hud handler to handle both the health system(which is incomplete as it doesn't include a death state yet) and displaying the hud.

Health Object
The Create Event code
global.hp = 99;
global.curexhp = 0;
global.currenthp = 0;
global.damage = 0;
global.totexhp = 0;
global.healing = 0;

The Step Event code
//Damage Handling
if (global.damage > 0)
{
global.currenthp = global.hp;
global.hp -= global.damage;
}

if (global.hp < 0)
{
if(global.curexhp > 0)
{
global.curexhp -= 1;
global.hp = 99-(global.damage-global.hp);
}
}

global.damage = 0;

//Healing Handling
if (global.healing > 0)
{
global.currenthp = global.hp;
while (global.hp < 100)
{
global.hp += sign(global.healing);
global.healing -= 1;
}
}

if (global.hp > 99)
{
if (global.totexhp > 0)
{
if (global.curexhp < global.totexhp)
{
global.curexhp += 1;
while (global.hp < 100)
{
global.hp += sign(global.healing);
global.healing -= 1;
}
global.hp = 0+(global.healing);
}
else
{
global.healing = 0;
}
}
else
{
global.healing = 0;
global.hp = 99;
}
}

global.healing = 0;

The Draw Event code
draw_set_halign(fa_left);
draw_set_valign(fa_middle);
draw_set_color(c_white);
draw_set_font(fnt_small);
draw_text(32,32,global.hp);
if (global.totexhp > 0)
{
if (global.curexhp >= 1)
{
draw_sprite(exhp1,-1,64,32);
}
else
{
draw_sprite(exhp2,-1,64,32);
}
}

The Spike Object(used to test damage)
The Create Event Code
//initialize variables
dam_deal = 3;

The Step Event Code
//damage step
if (place_meeting(x,y,Playerobj))
{
if (Playerobj.y < y-16)
{
with (Playerobj) vsp = -jumpspeed/2;
global.damage = dam_deal;
}
}

As you can see, the damage is supposed to only be calculated once, and in testing it does only run once, other than when the player is at full health, if the player is at full health then for some reason it deals damage twice. This applies to both having totally full 99 health, and to having a energy tank get emptied to reach 99 health again. I'm not sure why it's doing this. Healing items currently seem to work just fine with no issues.
 

origamihero

Member
It looks like the place_meeting check in your last code snippet is called twice because it most likely takes two steps for your player object to move out of the collision area of the spike. You can test and verify this by putting a show_debug_message in the place_meeting check.

I'd suggest adding a delay timer between hit detections, like blinking after getting damaged as seen in a lot of platform games.
 
A

Akari_Enderwolf

Guest
It looks like the place_meeting check in your last code snippet is called twice because it most likely takes two steps for your player object to move out of the collision area of the spike. You can test and verify this by putting a show_debug_message in the place_meeting check.

I'd suggest adding a delay timer between hit detections, like blinking after getting damaged as seen in a lot of platform games.
I was planning to add that for the game I made this health system for, but don't fully know how to do it yet. I also wanted to add momentum but only when in the air, that way I could add a small amount of negative momentum when the player is hurt so they bounce away from trouble rather than being able to use spikes as just a bouncy floor to get across pits(using the current example). I'm not 100% sure how to do momentum yet either.
 

origamihero

Member
Try tackling one problem at a time. Maybe focus on moving objects around first. In platformers, you'll want to use two variables that keep track of the horizontal and vertical speeds. You then can use these variables to move your object around, and add the necessary calculations for momentum. Then you can move on to having the objects react to collisions with momentum changes.

Can you make an object fall and increase its falling speed at the moment?
 
A

Akari_Enderwolf

Guest
Try tackling one problem at a time. Maybe focus on moving objects around first. In platformers, you'll want to use two variables that keep track of the horizontal and vertical speeds. You then can use these variables to move your object around, and add the necessary calculations for momentum. Then you can move on to having the objects react to collisions with momentum changes.

Can you make an object fall and increase its falling speed at the moment?
Yes, I followed Shaun's tutorial on the basics of a platformer and have that all set up right now. hsp for left and right vsp for up and down. I was planning on also looking into the variable jump height tutorial to add in a bit of a better sense of gravity. Another thing I will need to look into is water physics(affecting gravity) but I know that needs to come after momentum and all regular movement physics. Plus I feel i can handle it with a simple "if" statement checking for water collision and adjusting the variables accordingly. I want to get normal physics working first however.
 

origamihero

Member
Okay, sounds good. Then your next step would be keeping track of the player status. Is it on the ground, jumping, walking on the floor, or currently jumping and so on are the basics. You probably can check for these already.

What I like doing is using a variable on my player object that keeps track of how my player is supposed to move. It usually is a string. It stores things like 'normal', 'climbing', 'attacking', 'gothurt' and so on. Depending on this status, player controls are locked. For example, if the 'gothurt' status is active, the player controls are locked until it goes back to 'normal'. You can just check if the string is 'gothit' and add custom momentum calculations, like making him bounce away from objects.
 
A

Akari_Enderwolf

Guest
how would I set up that system?
Is it just adding an "else" statement to the "if" statement for vertical collision?

Here's how the code for movement looks right now. The code here is currently just as it was from the tutorial.
//get the player's input
key_right = keyboard_check(vk_right)
key_left = -keyboard_check(vk_left)
key_jump = keyboard_check_pressed(vk_space);

//react to inputs
move = key_left + key_right;
hsp = move * movespeed;
if (vsp < 10) vsp += grav;

if (place_meeting(x,y+1,wallobj))
{
vsp = key_jump * -jumpspeed;
}

//horozontal collision
if (place_meeting(x+hsp,y,wallobj))
{
while(!place_meeting(x+sign(hsp),y,wallobj))
{
x += sign(hsp);
}
hsp = 0;
}

//vertical collision
if (place_meeting(x,y+vsp,wallobj))
{
while(!place_meeting(x,y+sign(vsp),wallobj))
{
y += sign(vsp);
}
vsp = 0;
}

x += hsp;
y += vsp;
 

origamihero

Member
It seems all you need is additional if statements for how the hsp and vsp values are altered. For example, if your character is overlapping a ladder object, you'd only allow vertical movement.
 
P

ParodyKnaveBob

Guest
Howdy, A_E, and welcome to the GMC. $:^ ] First lemme say, thank you for an excellent question post; I hope others can learn from this; post your summarized issue, what you want to happen, what you do to make it happen, and what actually happens including what goes right and what goes wrong. Beautiful.

That said, I have very little help to give, but I hope you and your kind assistant here can get something out of it. $:^ ]
As you can see, the damage is supposed to only be calculated once, and in testing it does only run once, other than when the player is at full health, if the player is at full health then for some reason it deals damage twice. This applies to both having totally full 99 health, and to having a energy tank get emptied to reach 99 health again. I'm not sure why it's doing this. Healing items currently seem to work just fine with no issues.
My advice: use GM's awesome debugger. Place a breakpoint on the one line dam_deal = 3; and run the game with F6 (red play button). As soon as your two relevant instances collide, the debugger will pause the game, open up the code to that very line, and let you step through the code line-by-line (F10 I believe). You should be able to at least find where and when it goes wrong, which usually will soon lead to why.
What I like doing is using a variable on my player object that keeps track of how my player is supposed to move. It usually is a string. It stores things like 'normal', 'climbing', 'attacking', 'gothurt' and so on.
Lovely tip, and now I'll see if I can't up your ante some for the future. $:^ ]

macro list ...
NORMAL = 0
CLIMBING = 1
etc.

or enum list ...
Code:
enum Fsm {
  Normal,
  Climbing,
  // etc.
}
(And to spell it out just in case, this gives you all the self-commenting readability of a string with the raw simplicity of literal magic numbers and the accessibility (and auto-spell-check) of the code completion pop-up. Good stuff right there.)

Regards, folks,
Bob $:^ J
 
A

Akari_Enderwolf

Guest
I've never really used either of those. My basic understanding of programming came from High School flash programming and College "website" coding. Is there a good place you could direct me so I could read up on, and better understand how to use a enum list or a macro list?

Either one sounds like a good way to check for states(note, I have not yet watched the tutorial on "state machines" from Shaun)
 
P

ParodyKnaveBob

Guest
The ParodyKnave in me wants to type
and post as-is. $E^ }

But jokes aside, just open up the manual. If you don't find stuff in the table of contents easily enough, there are very useful index and search tabs, too. F1 (i.e. the manual) will tell you how to use the debugger, and just for the sake of it I tried typing enum and then macro into the index, and surely enough, a detailed enum section on the Data Types page, and a whole page dedicated to macro use.

I hope this helps,
Bob $:^ J
 
A

Akari_Enderwolf

Guest
The ParodyKnave in me wants to type

and post as-is. $E^ }

But jokes aside, just open up the manual. If you don't find stuff in the table of contents easily enough, there are very useful index and search tabs, too. F1 (i.e. the manual) will tell you how to use the debugger, and just for the sake of it I tried typing enum and then macro into the index, and surely enough, a detailed enum section on the Data Types page, and a whole page dedicated to macro use.

I hope this helps,
Bob $:^ J
cool, thank you for having the patience to leave thought out responses. And thank you for letting me know where to find info on it.
 
A

Akari_Enderwolf

Guest
ok, i read the manual about enum stuff I still don't fully understand how to use them.
I only found a single page about it.
 
P

ParodyKnaveBob

Guest
~nodnod~ As I said, a detailed section of a page. Do you have any specific questions? I'm not sure I can give any better overview than the manual did.

Just remember that my note about macros and enums was pointed more toward origamihero who suggested using player states in the first place. My only real point was that macros and/or enums can be better than strings for various reasons. As for your purposes, if you learn well from all these newfangled video tutorials, then you might look up "finite state machine" or perhaps "player states" if that gives any results. A quick overview would be something like...

Code:
// Game Start Event (or some other one-time event)
enum States {
  Idle,
  Walk,
  Run,
  Reel,
  Jump,
  Swim,
  // etc.
}

// Create Event for player object
index_state = States.Idle;

// Step Event, Keyboard Left/Right/etc. Event, whatever works, for player object
// 1. check if state makes movement available
// 2. check for input causing movement
// 3. move player instance
index_state = States.Walk; // or Run or whatever works here

// End Step Event
switch (index_state) {
  case States.Idle:
    // set idle animation
    break;
  // do this for all cases
}
Off the top of my head anyway. I hope this continues nudging you in the right direction. $:^ ]
Bob

EDITS:

P.S. Sorry, I suppose I should've made clear: My concept code creates a list of automatically enumerated values going 0, 1, 2, etc. These enums are unchanging, constant, all throughout your code, and you never have to bother knowing which name holds which number. Then, the concept code creates a variable to set and read a state; that variable of course changes all willy-nilly depending on player input, enemy interactions, etc. (Isn't it nice that any player or enemy or what-have-you instance can use these enumerated states? $:^ ] Ohh yeah.)

P.P.S. Well, I suppose one could also set index_state_previous = index_state in Begin Step Event, in which case one could check if (index_state != index_state_previous) in the End Step before bothering with animation settings.

P.P.P.S. Just don't forget to initialize the animation in the Create Event first. $;^ D
 
Last edited by a moderator:
A

Akari_Enderwolf

Guest
That actually kinda helps more than the page in the manual did. The example in the page was a single line and didn't really give a clear understanding for me.
The example of using the enum i mean.

I also just started watching the state machine tutorial from Shaun, and he goes into the enum stuff in that video.
 
Last edited by a moderator:
A

Akari_Enderwolf

Guest
I attempted something to try to stop the glitch but it's still occurring
I changed the damage step code like this
//damage step
if (place_meeting(x,y,Playerobj) && global.damage = 0)
{
if (Playerobj.y < y-16)
{
with (Playerobj) vsp = -jumpspeed/2;
global.damage = dam_deal;
}
}
The check for global.damage being 0 should have stopped the script from triggering more than once since after the first step triggers it can't add more damage because there was already one thing added.

I'm also now experiencing the game freezing when I touch a healing item.
I can't figure out why the game is freezing. There has been no change to the health object or the health pickup.
This script runs when the player collides with the health pickup.
global.healing += 10;
instance_destroy();
 

Yal

🐧 *penguin noises*
GMC Elder
I think your problem arises because you treat remaining energy tanks and HP as two separate variables... this adds extra complexity, and complexity is the breeding ground for mistakes. Is there any reason you can't just compute the number of energy tanks as "HP / 100, rounded downwards" and only really use the number of energy tanks as a display thing?
 
A

Akari_Enderwolf

Guest
I think your problem arises because you treat remaining energy tanks and HP as two separate variables... this adds extra complexity, and complexity is the breeding ground for mistakes. Is there any reason you can't just compute the number of energy tanks as "HP / 100, rounded downwards" and only really use the number of energy tanks as a display thing?
Not quite sure I understand what you mean. The energy tanks are permanent power ups, so the game needs to be able to keep track of how many of those the player has. That's why it stores the number of those separately. If I didn't store these separately then there would be a large array of if statements just to detect "is player health at 200? 300? 400?" and so on. At least that's my understanding of what you're saying. The Energy Tanks are increasing the max health. Other than them there is effectively no level system in the game.

With the current setup I have going the player's first damage is 12 points(increased the damage to 6 for testing) and the healing item should take that to 97, instead it freezes. I've even tried getting damage twice and then healing but get the same result, the game freezes.
 
P

ParodyKnaveBob

Guest
My stock advice in a case like this:

In your player-health-collision, place a breakpoint at the first line of code. Run in debug mode and when you pick up the health item (and the Debugger auto-pauses the game), step through the code line-by-line to see at what point you get caught in an infinite loop (or whatever's happening).

I hope this helps,
Bob $:^ J
 
A

Akari_Enderwolf

Guest
My stock advice in a case like this:

In your player-health-collision, place a breakpoint at the first line of code. Run in debug mode and when you pick up the health item (and the Debugger auto-pauses the game), step through the code line-by-line to see at what point you get caught in an infinite loop (or whatever's happening).

I hope this helps,
Bob $:^ J
Thank you, I was able to find the loop point. Turns out I forgot to check for something when healing that stops the healing when the "healing" reaches 0, this is causing the game to heal the 10 points then infinitely remove health. I still haven't figured out the double health loss glitch but knowing about breakpoints now I'm going to take a look at that next.

I saw what's happening, the reason my fix didn't work is because the player object on the first hit is staying in the spike object too long and getting hit twice. I'll need to actually set up the damage/i-frames state in a state machine to fix this most likely and not tie in the prevention method to the global damage variable.
 
Last edited by a moderator:

Yal

🐧 *penguin noises*
GMC Elder
Not quite sure I understand what you mean. The energy tanks are permanent power ups, so the game needs to be able to keep track of how many of those the player has. That's why it stores the number of those separately. If I didn't store these separately then there would be a large array of if statements just to detect "is player health at 200? 300? 400?"
Not really, you could just use maths. With a max health variable and a current health variable, the number of total energy tanks is maxHealth div 100 and the number of filled ones is CurrentHealth div 100. (div being the operator for integer division). The health you have "in the current tank" is CurrentHealth mod 100. (mod is the operator for the remainder after integer division, aka "what's left after splitting up") So you'd just draw that number of tanks in the HUD, etc, and the health number next to them. You'd start out with 99 MHP (max HP) and get 100 more for each energy tank you collect.

To explain it in a different way... instead of actually coding the separate energy tanks getting added, you have a single energy tank that gets bigger each time you get an upgrade, and you just pretend there's several individual energy tanks.
 
A

Akari_Enderwolf

Guest
Not really, you could just use maths. With a max health variable and a current health variable, the number of total energy tanks is maxHealth div 100 and the number of filled ones is CurrentHealth div 100. (div being the operator for integer division). The health you have "in the current tank" is CurrentHealth mod 100. (mod is the operator for the remainder after integer division, aka "what's left after splitting up") So you'd just draw that number of tanks in the HUD, etc, and the health number next to them. You'd start out with 99 MHP (max HP) and get 100 more for each energy tank you collect.

To explain it in a different way... instead of actually coding the separate energy tanks getting added, you have a single energy tank that gets bigger each time you get an upgrade, and you just pretend there's several individual energy tanks.
still not sure I understand... which kinda frustrates me because math was one of my better subjects back in high school. I'm kinda a novice when it comes to programming.

to my understanding i will still need roughly the same number of if statements to control each individual displayed tank, which is probably part of what's making it hard for me to understand since it feels like the current system works well with having the if statements for each displayed tank. Right now I'm using 2 sprites to represent every tank, one for when it's full, one for when it's empty.

Sorry if I'm causing any frustration. I'm also posting this earlier in the day than I usually wake up because I had to get up early today. So I'm not thinking at full capacity right now.
 
P

ParodyKnaveBob

Guest
A_E, do you understand what div and mod do technically and in application? What Yal's saying is you can do stuff like this:

get another energy tank -> max += 100; -> max is now 399
max div 100 -> 3 because div is integer division .. no silly decimals here! $:^ J .. and no extra variables to constantly keep track of b/c 399 health means 3 tanks

quick, sloppy pseudo code:
Code:
var _count_tanks = max div 100,
    _i_tank;
for (_i_tank = 0; _i_tank < _count_tanks; ++_i_tank) {
  draw(spr_tank, (current div 100 >= _i_tank), x, y);
}
...or something like that, which isn't even as pseudo as I intended. But! No ifs necessary. $:^ ] Both mod and div create magic.

I hope this helps,
Bob
 
A

Akari_Enderwolf

Guest
A_E, do you understand what div and mod do technically and in application? What Yal's saying is you can do stuff like this:

get another energy tank -> max += 100; -> max is now 399
max div 100 -> 3 because div is integer division .. no silly decimals here! $:^ J .. and no extra variables to constantly keep track of b/c 399 health means 3 tanks

quick, sloppy pseudo code:
Code:
var _count_tanks = max div 100,
    _i_tank;
for (_i_tank = 0; _i_tank < _count_tanks; ++_i_tank) {
  draw(spr_tank, (current div 100 >= _i_tank), x, y);
}
...or something like that, which isn't even as pseudo as I intended. But! No ifs necessary. $:^ ] Both mod and div create magic.

I hope this helps,
Bob
It's starting to make a bit more sense, and would eliminate the need for checking if the health is over 99 or less than 0 to change the number of full tanks displayed.
I kinda understand the div stuff now, but not sure what's going on in the code with the "_i_tank" bit of the code.
like, I'm reading it as "_i_tank is 0, _i_tank is less than _count_tanks, add _i_tank to itself"
does the "draw(spr_tank, (current div 100 >= _i_tank), x, y);" code mean it draws, in the example, 3 of spr_tank in a row without overlap?

I won't be able to respond for a few hours, I need to head out the door now.
 

Yal

🐧 *penguin noises*
GMC Elder
It's starting to make a bit more sense, and would eliminate the need for checking if the health is over 99 or less than 0 to change the number of full tanks displayed.
Indeed. It takes a while to get into the mindset, but once you know how to make code do stuff for you, you'll save a lot of work. As a rule of thumb, if you need to copypaste a block of code and change a little bit more than once in a row, you're doing stuff in a more complicated way than necessary.

I'd drop in an example here, but @ParodyKnaveBob's pseudocode is kinda sufficient... max isn't a valid variable name since there's a function named max, but other than that you should be able to use that to draw your energy tanks (modified to use the actual name of the sprite in the code, and have image 0 be the empty tank and image 1 the full tank).
 
A

Akari_Enderwolf

Guest
Indeed. It takes a while to get into the mindset, but once you know how to make code do stuff for you, you'll save a lot of work. As a rule of thumb, if you need to copypaste a block of code and change a little bit more than once in a row, you're doing stuff in a more complicated way than necessary.

I'd drop in an example here, but @ParodyKnaveBob's pseudocode is kinda sufficient... max isn't a valid variable name since there's a function named max, but other than that you should be able to use that to draw your energy tanks (modified to use the actual name of the sprite in the code, and have image 0 be the empty tank and image 1 the full tank).
in my game the max health would be listed as AR_maxhp and PS_maxhp, because the player technically has 2 health bars, one in armor, one out of armor.

I still don't really understand what's going on with the _i_tank part of the psudocode however.
 
P

ParodyKnaveBob

Guest
It appears you haven't learned the wonders of the for loop yet. You can go read up on it in the manual, and here's my quick paraphrase/summary:

for (initialize a variable ; if this condition is true then execute the loop ; after each loop do this ) { do this in the loop }

The decades-old convention is to use i to increment through for loops, but I like my self-commenting code to say more than that, hence i_tank ... and then also my convention is to prefix local variables with _ to immediately distinguish everything on sight, hence _i_tank in the end.

Also, as for the x,y coordinates in the drawing function, you'd likewise use an offset with _i_tank to draw each tank in a different spot on the screen.

Bob $:^ J
 
A

Akari_Enderwolf

Guest
It appears you haven't learned the wonders of the for loop yet. You can go read up on it in the manual, and here's my quick paraphrase/summary:

for (initialize a variable ; if this condition is true then execute the loop ; after each loop do this ) { do this in the loop }

The decades-old convention is to use i to increment through for loops, but I like my self-commenting code to say more than that, hence i_tank ... and then also my convention is to prefix local variables with _ to immediately distinguish everything on sight, hence _i_tank in the end.

Also, as for the x,y coordinates in the drawing function, you'd likewise use an offset with _i_tank to draw each tank in a different spot on the screen.

Bob $:^ J
ok, just to check my understanding. Say spr_tank is 4 pixels wide, and i wanted to draw 3 of them, I would put like x+4 or something in this line?
Code:
draw(spr_tank, (current div 100 >= _i_tank), x, y);
and that would make each tank draw 4 pixels further to the right?
 
P

ParodyKnaveBob

Guest
Nope. $:^ ]

If you used x+4 to say...

draw(spr_tank, (currentHP div 100 >= _i_tank), x+4, y);

...then in each loop as _i_tank goes from 0 to maxHP div 100, the result would be...

draw(spr_tank, (currentHP div 100 >= 0), x+4, y);
draw(spr_tank, (currentHP div 100 >= 1), x+4, y);
draw(spr_tank, (currentHP div 100 >= 2), x+4, y);
etc.

...which shows only the tank-emptiness changing each loop, but the x placement stays the same. Therefore, there must be something else to put there to see a change in every loop. Hm? $:^ ]

Bob
 
Last edited by a moderator:
A

Akari_Enderwolf

Guest
so...
Code:
draw(spr_tank, (current div 100 >= _i_tank), x+offsetA, y);
offsetA + 4;
and initialize the offset to however far to the right the first tank should appear?
Is there a way to make the offset be based on the player's view? I don't plan on them seeing the entire room all at once. Asking about this only because it relates to how to set up the offsets and stuff.
 

Yal

🐧 *penguin noises*
GMC Elder
so...
Code:
draw(spr_tank, (current div 100 >= _i_tank), x+offsetA, y);
offsetA + 4;
and initialize the offset to however far to the right the first tank should appear?
Correct :3

And yes - there's a set of global variables for the view. view_xview and view_yview refer to the left edge and top edge coordinate, respectively, and view_wview and view_hview to its width and height. (Side note: these refer to the first view, aka view 0 - if you use multiple views you can index these as an array to get view 1, view 2 and so on, but most of the time view 0 is enough).
So, initialize offsetA to view_xview + 64, for instance, and the tanks will be drawn 64 pixels into the view horizontally.
 
P

ParodyKnaveBob

Guest
You're getting closer. $:^ J
  • Note, I was only using "x" and "y" in that pseudo-code as shorthand. If you actually put x,y in that function, it'll just plop the graphics down relative to where you've put the instance in the room.
  • You shouldn't need yet another offset variable. You should be able to reuse the _i_tank variable. Have you actually tried this in the game yet? If you do your own experimenting, you can get a lot more experience right away...
As for your next question:
  • Yes. You can see all the view_*[] arrays in the manual. Something like view_xview[..] off the top of my head. In the future, a lot of questions like this can be answered by looking in the manual at various functions directly related to what you're already working with.
  • However, for your GUI, you shouldn't need view-finagling in the least. The Draw GUI event was built for this exact reason. $:^ ]
I hope this helps,
Bob

P.S. Oh, hi, Yal. $:^ P (I'd typed this a good few hours ago but fell asleep just before hitting send. Whoops.)
 
A

Akari_Enderwolf

Guest
I didn't have time yesterday to look at the manual, I had to go to work not long after asking the question.
I didn't think of re-using the _i_tank variable at first for some reason or another.

As for the view, I'm looking into trying to basically make a "track" that the view follows so I could potentially make L shaped rooms without the camera going outside the room unless I want it to move over(like if the player goes in a secret room) I also plan to have the tank display start a certain distance from the left of the screen. so like X+(_i_tank*4)+32 or something.
 
Top