This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Dave-ee Jones's profile picture

[Beta] Dodj - Dodgeball in CC!

Started by Dave-ee Jones, 04 September 2017 - 05:51 AM
Dave-ee Jones #1
Posted 04 September 2017 - 07:52 AM
Dodj
Dave-ee's Dodgeball for CC!

Look! It's dodgeball!
Yes it is! And it's completely working in every way (once you get catching-the-ball down pat).

The game has support for an unlimited amount of players natively. You can add more players by adding entries into the player settings table at the top of the program.

The ball changes colour based on what team threw it, so you can know whether to dodge it or try and catch it to throw it again. The ball also gets thrown in the direction you last moved, so you can do some fancy throw-it-against-the-back-wall shot. However it still bounces 1:1 in the direction it came from so no corner shots or anything like that, sadly. Might make an 8-direction play-style sometime.

The ball's speed can be adjusted in the settings at the top, just increase the frames per second.
You can change a player's team by changing the colour of a player in the player settings.

Usage:

dodj help -- displays the help text
dodj <players> -- choose the amount of players (need to setup controls for players 3 and up)


Controls:
PLAYER 1 (default):
W, A, S and D to move around, Left Shift to pickup/throw the ball, Z to stop moving when pressing a key (change direction instead of moving in a direction).

PLAYER 2 (default):
Arrow keys to move around, O to pickup/throw the ball (or catch it..) and P to stop moving.

Things you need to know if you want to be better at this game:
  1. You can only catch the ball if you are facing where the ball is coming from.
  2. Don't hold the action button after you have caught the ball, otherwise when you release it it will instantly throw the ball. Just press it right before the ball reaches you.
Changelog:
SpoilerPRE-ALPHA -> ALPHA
- Completely re-coded for optimisation, gameplay and customisation
- Bug fixes (well, duh, I re-wrote the whole thing)
- Players are now 2 pixels high instead of 1
- Sprites (for players and balls)
- Support for items (apart from balls - this could potentially be an API-kinda thing)
- Support for more players (virtually? infinite. physically? depends how big your keyboard is..)
- Command line arguments ('help' and <players>)

ALPHA -> BETA
- Added a scoreboard which is drawn up the top, positions are calculated based on the number of teams
- Players' speeds can now be adjusted, allowing for some..interesting..modifications
- Support for a virtually unlimited amount of teams
- Code changes here and there
- Optimisation (mostly based on code over the different 'versions' of Beta)


Pastebin:

pastebin get zsijDWCe dodj



PLEASE give suggestions and feedback! :D/>


I wish I had thought of this earlier for CCJam, would have been a pretty good entry as I haven't seen any other games like this yet. Might do in the near future, haha!
Edited on 12 September 2017 - 02:46 AM
Dave-ee Jones #2
Posted 06 September 2017 - 03:38 AM
Updated! Now has a player 2, scoreboard, better (and more fair) catching mechanics, etc. (and less bugs).

Thinking about re-writing it because the code is VERY messy.
Bomb Bloke #3
Posted 06 September 2017 - 04:35 PM
If you're on at least CC1.76, "\1" or "\2" might make for more interesting "player characters" than "1" or "2". You may also consider "\7" for your ball, perhaps.

In a conditional statement, anything that isn't false or nil counts as true (and "not"s to false). This means that you don't really need separate "PLAYER_1.CATCHING_CD" and "PLAYER_1_CD_CATCH_TIMER" values. For example:

(key section:)
if not PLAYER_1.CATCHING and not PLAYER_1.CATCHING_CD then
	PLAYER_1.CATCHING = os.startTimer(0.5)
end
.
.
.
(timer section:)
elseif button == PLAYER_1.CATCHING then
	PLAYER_1.CATCHING = false
	PLAYER_1.CATCHING_CD = os.startTimer(3)
elseif button == PLAYER_1.CATCHING_CD then
	PLAYER_1.CATCHING_CD = false

Do you intend to add an AI player? The basic routines feel like they should be pretty simple - move towards a ball, attempt to catch it, move to the same row/col as the opponent, throw, repeat with other ball…
Edited on 06 September 2017 - 02:36 PM
nitrogenfingers #4
Posted 07 September 2017 - 05:15 AM
I had a quick play with this. There's definitely potential for something really fun here, but a few things I'd suggest:

- The ability to change your facing without moving. A second button could do this without overloading the scheme I think.
- A smaller playfield. Right now it's just too easy to miss unless you're quite close
- More dodgeballs on the field. I'm not sure what a good ratio would be but I think it should be reasonably difficult to move around when all are in motion.
- Tweak catching. I found it really challenging to catch the ball- either the game is moving too fast or the window to catch is just too small. Some visual indication of when/where that window is would be really good as well.

As a later deliverable one idea that came to me is to have playfields with obstacles that reflect the dodgeballs in different directions- you could use slashes or teletext characters to bounce the balls 90 degrees. Particularly if these obstacles could be moved, that could lead to some interesting gameplay.
Another layer might be having multiple players- either you move them all at once with the same directions or cycle between them. That could really raise the stakes of the game, make it like spinning plates.


Also I noticed some flicker when I played it. It looks like you're redrawing the game each cycle- you can probably trim this down as there aren't many visual elements on the screen- like only draw a player when an appropriate key event is fired etc.
Dave-ee Jones #5
Posted 07 September 2017 - 06:04 AM
I had a quick play with this. There's definitely potential for something really fun here, but a few things I'd suggest:

- The ability to change your facing without moving. A second button could do this without overloading the scheme I think.
- A smaller playfield. Right now it's just too easy to miss unless you're quite close
- More dodgeballs on the field. I'm not sure what a good ratio would be but I think it should be reasonably difficult to move around when all are in motion.
- Tweak catching. I found it really challenging to catch the ball- either the game is moving too fast or the window to catch is just too small. Some visual indication of when/where that window is would be really good as well.

As a later deliverable one idea that came to me is to have playfields with obstacles that reflect the dodgeballs in different directions- you could use slashes or teletext characters to bounce the balls 90 degrees. Particularly if these obstacles could be moved, that could lead to some interesting gameplay.
Another layer might be having multiple players- either you move them all at once with the same directions or cycle between them. That could really raise the stakes of the game, make it like spinning plates.


Also I noticed some flicker when I played it. It looks like you're redrawing the game each cycle- you can probably trim this down as there aren't many visual elements on the screen- like only draw a player when an appropriate key event is fired etc.

Thanks for the feedback and suggestions. I have thought about a few of these - but I'm not sure how you would have the second button work (to rotate your character). Press it and use the arrow keys to point in a direction? That would be fairly easy, a 'while-button-down-don't-move-when-you-press-arrow-key'.

Smaller playfield would definitely be more interesting, but it wouldn't really make use of the screen unless I made some kind of scoreboard or screen that displays more information or something to fill it up.

More dodgeballs is in the works, definitely. I was thinking it would be more difficult for players if it was basically a moving minefield - make it more interesting. So maybe a 2:1 ball:player ratio?

Catching is indeed difficult. Just remember to press the catch button BEFORE it reaches your player, then release the catch button RIGHT AFTER you catch it. Any harder than that makes it very hard, and any easier makes it almost cheating.

In regards to moveable playing fields - I am definitely interested in that. I had actually made it so you could pickup basically anything at the start, but made it so it only registered you trying to pickup a ball - but I'll definitely look into picking up blocks and redirecting pads. I was also thinking about making it a split playing field, having the Blue team stick to the left and the Red to the right.

The flicker is due to me making a quick, working version of the program. If you look at the code it is quite messy, as Bomb Bloke has also pointed out. How would you draw it? You still have to draw the players after you clear the screen (needs to be cleared otherwise old player positions will be showing).

Do you intend to add an AI player? The basic routines feel like they should be pretty simple - move towards a ball, attempt to catch it, move to the same row/col as the opponent, throw, repeat with other ball…

It's a good idea, but probably closer to the bottom of the list right now. Sorry :(/>
Bit harder and the actual gameplay of the game is a bit more pressing.

EDIT: What do you guys think about a name-change? :P/> Dodj? I can't actually do any of this right now but I'll definitely try get to it over the next few days.

EDIT2: (Ideas keep poppin') What if there was a build mode before the game starts? You can place 5 blocks (redirecting, teleporting - teleports the ball as it goes in one block and comes out the other, maybe going into a redirecting block back to the other side to confuse the other team - and maybe some others) around your side of the field (or over the whole field) to make some crazy pinball-like combos. The mode would last about 30s to a minute, then the game starts.
Edited on 07 September 2017 - 04:49 AM
Dave-ee Jones #6
Posted 07 September 2017 - 06:45 AM
How would I do a mass call of functions for each ball? Can I do something like this?

for k, v in pairs(Balls) do
  if k.X then
	k:reset()
  end
end
It's just to go through each ball and reset it. Would make it easier and far more convenient if I was to spawn balls during gameplay. At the moment I can't because I have to manually check collision and manually update them one-by-one.

I want to do something similar for players as well.

EDIT: If you want you can have a look at the initial progress of the "new and improved" version I uploaded. So far there aren't many changes, but I'm aiming to make it smoother in terms of both code and gameplay. Notice I changed the HOLDING variable to ITEM as well, to pick up blocks (maybe even fake balls that are actually bombs and when you catch them you blow up and lose a life..Hehe). I also made the Player keybindings at the top in the configuration area, just in case you want to play around with a better control scheme.
Edited on 07 September 2017 - 05:28 AM
Bomb Bloke #7
Posted 07 September 2017 - 09:06 AM
Smaller playfield would definitely be more interesting, but it wouldn't really make use of the screen unless I made some kind of scoreboard or screen that displays more information or something to fill it up.

An alternative is to simply make the players - and perhaps the balls - larger.

How would you draw it? You still have to draw the players after you clear the screen (needs to be cleared otherwise old player positions will be showing).

You don't need the clear the whole screen to hide their old positions - you only need to redraw just those characters.

However, a quick'n'dirty method of preventing flicker is to make use of the setVisible function in the window buffer multishell is running your script in:

local function drawFrame()
  term.current().setVisible(false)
  -- clear the terminal, draw stuff, do whatever you like.
  term.current().setVisible(true)
end

This relies on your script running within a window. You can place a check near the top of your script to make sure that'll be the case:

if not term.current().setVisible then term.redirect(window.create(term.current(), 1, 1, term.getSize())) end

It's still more efficient to just not do full screen redraws, though.

How would I do a mass call of functions for each ball? Can I do something like this?

That method's fine.
Dave-ee Jones #8
Posted 07 September 2017 - 11:47 PM
Yeah I guess I could make them larger, though the balls would have to stay the same otherwise they be huge (if they needed to be circle-ish).

In regards to redrawing characters instead of the whole screen, won't the characters still draw where they were last time? I guess I could draw an "empty" pixel over where they were last time, but what ifa ball's there? Or another player? Or a wall..

However, a quick'n'dirty method of preventing flicker is to make use of the setVisible function in the window buffer multishell is running your script in:

local function drawFrame()
  term.current().setVisible(false)
  -- clear the terminal, draw stuff, do whatever you like.
  term.current().setVisible(true)
end

Oh boi. Is that supposed to update things without updating things on-screen then it instantly redraws it over everything on-screen when you set to visible again? Wouldn't you still notice a flicker or even lag inbetween keyboard inputs?
Edited on 07 September 2017 - 09:49 PM
Bomb Bloke #9
Posted 08 September 2017 - 12:44 AM
In regards to redrawing characters instead of the whole screen, won't the characters still draw where they were last time? I guess I could draw an "empty" pixel over where they were last time, but what ifa ball's there? Or another player? Or a wall..

If a player can be standing where a wall's supposed to be, then you've got other problems to deal with before you worry about rendering…

But a simple order of operations is to wipe every area of the screen where a mobile was, and then redraw all the mobiles.

Oh boi. Is that supposed to update things without updating things on-screen then it instantly redraws it over everything on-screen when you set to visible again? Wouldn't you still notice a flicker or even lag inbetween keyboard inputs?

The flicker is caused by you wiping the screen and then taking too long to draw everything back (because you're drawing way more stuff than you need to). The user is exposed to your cleared terminal while in the moment it takes to fill it back up again.

Setting the buffer to invisible causes it not to render its contents the your screen until you set it to visible again. When you do so, it doesn't clear the screen before redrawing - it just straight-up redraws. Most of the screen effectively remains unchanged, and so there's no flicker.
Dave-ee Jones #10
Posted 08 September 2017 - 01:11 AM
Setting the buffer to invisible causes it not to render its contents the your screen until you set it to visible again. When you do so, it doesn't clear the screen before redrawing - it just straight-up redraws. Most of the screen effectively remains unchanged, and so there's no flicker.

It makes sense, but wouldn't you still notice a bit of lag?

But a simple order of operations is to wipe every area of the screen where a mobile was, and then redraw all the mobiles.

Yep, I'll do that. I was just making it look more confusing in my head, haha.

EDIT1: (Dev version) I've made tables for players/balls so making many of each is a possibility now. However there is only keybinding support for players 1 and 2.

EDIT2: Pretty happy about player movement right now. It's sooo smooth. I wish I could upload a video but not the time and place. Just have a look at this pastebin post. I did notice that if you put the 2 players over the top of one another they will block each other out (because they clear where they were), but I'll just draw ALL players after they move instead of just themselves.

EDIT3: Might put the player sprites/keybindings in a table so that you can configure them far easier, allowing you to add a virtually infinite amount of players (if you really wanted to). Instead of having a set "if this new player has the index of 2 give it these settings: etc.." it will reference the table's index tables. That way you can add player settings as you want, and add as many as you want.
Edited on 07 September 2017 - 11:37 PM
Bomb Bloke #11
Posted 08 September 2017 - 01:44 AM
It makes sense, but wouldn't you still notice a bit of lag?

Ultimately code doesn't execute instantly - the best you can do is to minimise the amount that gets pumped through your processor. That means a frame update should clear just the bits that need clearing, and redraw just the bits that need redrawing. Doing that, it's unlikely anyone'll notice any issues.
Dave-ee Jones #12
Posted 08 September 2017 - 01:59 AM
It makes sense, but wouldn't you still notice a bit of lag?

Ultimately code doesn't execute instantly - the best you can do is to minimise the amount that gets pumped through your processor. That means a frame update should clear just the bits that need clearing, and redraw just the bits that need redrawing. Doing that, it's unlikely anyone'll notice any issues.

Yeah. As you say, it's a quick 'n' dirty way of making a bad way of drawing a bit better. So far, the way I'm currently doing it (the 'clean-my-tracks' way) is very clean and neat. Not sure how it'll go when there are 2-8 balls flying around the screen with 4 players and walls being moved around, but I imagine it'll be far better than clearing the screen every 0.2 of a second or something like that.

I did a quick test of adding a player to my new way of storing players and adding players, and it is sooo easy. I literally just added a table for [3] in this table, and then created a player with id 3.
Spoiler

local t_PLAYER_SETTINGS = {
[1] = {
  LEFT = keys.a, -- Left key (press to move left)
  UP = keys.w, -- Up key (press to move up)
  RIGHT = keys.d, -- Right key (press to move right)
  DOWN = keys.s, -- Down key (press to move down)
  ACTION = keys.leftShift, -- Action key (press to pickup/throw/drop)
  STOP = keys.z, -- Stop key (press while pressing a move key to not move)
  SPRITE = "\1", -- Sprite (1 character)
  TEAM = colors.blue, -- Team, colour based. Defaults are blue and red.
  X = 3, -- X position at the start of the game and when players are reset
  Y = 3 -- Y position at the start of the game and when players are reset
},
[2] = {
  LEFT = keys.left,
  UP = keys.up,
  RIGHT = keys.right,
  DOWN = keys.down,
  ACTION = keys.o,
  STOP = keys.p,
  SPRITE = "\2",
  TEAM = colors.red,
  X = t_TERM.W - 3,
  Y = t_TERM.H - 3
},
[3] = {
  LEFT = keys.g,
  UP = keys.y,
  RIGHT = keys.j,
  DOWN = keys.h,
  ACTION = keys.u,
  STOP = keys.i,
  SPRITE = "\2",
  TEAM = colors.blue,
  X = 3,
  Y = t_TERM.H - 3
}
}
It uses far less code, looks neater, works faster and is completely modular in that I could make 4 different teams easily by just changing the colour of player 3 to green (as an example), or change the starting positions of players, the sprites etc.
Or you guys can to optimise or have a customised way of playing :P/>

But seriously, this code is way neater than the first. But I do this with every program I make - a working draft, and then I completely "re-code" the program much nicer and, preferably, with as much OOP as possible.

For now, I'll make the number of players an argument to running the program. E.g.

dodj <players>
And make a for loop that creates the amount of players. Ez. And yes, I updated the pastebin again.
Edited on 11 September 2017 - 12:16 AM
Dave-ee Jones #13
Posted 08 September 2017 - 06:43 AM
I need a hand with interacting with items (balls, blocks etc.) effectively. I don't want to do it "the old way" that DJBALL uses, because (as you can see in the code) it's messy and uses close to 100 lines in itself, and that only supported 2 players and 2 balls. The best way (I can think of) is having the balls look for a player on top of it every time it moves.

But I don't want a huge, messy if statement with hundreds of the same tests within different conditions. Pretty annoying problem :/

Conditions that the ball can be caught in:
  • If ball is white and sitting on the ground
  • If ball is any colour and the player is facing it
Conditions that the player can be hit in
  • If ball is NOT the same colour as the player and not white
EDIT: This is what I came up with. Hopefully I didn't miss anything.. I missed a lot.
Edited on 11 September 2017 - 03:56 AM
Dave-ee Jones #14
Posted 11 September 2017 - 05:55 AM
Updated to the new Alpha version! Completely re-coded. Check the changelog (and the code if you want).
nitrogenfingers #15
Posted 11 September 2017 - 06:38 AM
This is looking better, although I still can't catch a ball to save my life.

Bug: Once one team scores a point and the field resets, players are no longer able to pick up dodgeballs

I think my comment about the size of the field is still my big concern with this one, although having more dodgeballs on screen definitely makes it more frantic. A game mode where a moving dodgeball is dangerous to both sides would even further raise the stakes. You should have a display somewhere in the frame that shows the current score as well.

One last suggestion, particularly seeing as this game appears to be about fast movement, you might want to instead of using key events to move your characters, you could keep the state of the button as 'up' or 'down'- setting it to either when a key_up or key_down occurs. Then just have a running timer in the background and evaluate each movement when it ticks over. If you do that, you avoid that delay after the first key event fires and you have more control over how fast the characters move.


local player = { x = 10, y = 10 }
local keyboard_states = { [keys.up] = false, [keys.down] = false, [keys.left] = false, [keys.right] = false }
local key_timer_length = 10
local key_timer = os.startTimer(key_timer_length)
while true do
  local id, p1 = os.pullEvent()
  if id == "key_down" then keyboard_states[p1] = true
  elseif id == "key_up" then keyboard_states[p1] = false
  elseif id == "timer" and p1 == key_timer then
    if p1 == keys.up then player.y = player.y -1
    --etc etc
  end
end
Dave-ee Jones #16
Posted 11 September 2017 - 06:55 AM
Bug: Once one team scores a point and the field resets, players are no longer able to pick up dodgeballs

Yes, I found that bug before I uploaded the update and then completely forgot about it, lol. *whoosh* Over my head. :rolleyes:/>
Got any ideas why that is? I couldn't find out why. :/ Fixed.

In terms of screen space and players-be-hard-to-hit, I had an idea of making players a height of 3 (length more like) so it's completely birds-eye and the ball has to land in the middle of them (otherwise they get hit). Make it a bit more interesting and easier to hit players. But then there was the questions. "Should I make players rotate when they change direction?" (making issues when you draw 3 pixels one way, then swap the direction they are drawn in). "How does shooting from the side work?" or "How does getting hit on your 'shoulder' work?"

I'm planning on adding different items as well (fake balls that are actually bombs so when a player attempts to catch it it the player is 'hit'), blocks (walls or redirecting walls), and maybe some teleporting pads (ball goes in one way, comes out somewhere else).

One last suggestion, particularly seeing as this game appears to be about fast movement, you might want to instead of using key events to move your characters, you could keep the state of the button as 'up' or 'down'- setting it to either when a key_up or key_down occurs. Then just have a running timer in the background and evaluate each movement when it ticks over. If you do that, you avoid that delay after the first key event fires and you have more control over how fast the characters move.


local player = { x = 10, y = 10 }
local keyboard_states = { [keys.up] = false, [keys.down] = false, [keys.left] = false, [keys.right] = false }
local key_timer_length = 10
local key_timer = os.startTimer(key_timer_length)
while true do
  local id, p1 = os.pullEvent()
  if id == "key_down" then keyboard_states[p1] = true
  elseif id == "key_up" then keyboard_states[p1] = false
  elseif id == "timer" and p1 == key_timer then
	if p1 == keys.up then player.y = player.y -1
	--etc etc
  end
end

Woah. That's actually a really good idea..MAAAAAAAAAN. Now I have to redo my whole while loop :/ Luckily it's only like 50 lines (from the 200 or so from the first one..).

So I just implemented that in my game, and it is actually so funny when your player is moving every 1/30th of a second. He's sooooo fast! Haha. I think I'll halve that at least though..But it's editable for those who want to play around with it. (Variables: n_BALL_UPDATE_INTERVAL and n_PLAYER_UPDATE_INTERVAL)

In terms of score, I found that one difficult. Because I made the player's teams changeable it means that players can change their team via the colour, which means that if I have a set scoreboard and people change the colours they aren't going to be scored..Can't think of a way to implement this without having each player have their own score. Then I could have a bit of code that sees how many teams there are by looking at each player's team, then if 2 players (or more) are on the same team I can combine their score for the scoreboard. E.g. Player 1 has 1 hit, Player 3 has 2, so therefore their score is 3 (being on the same team).

Catching the ball is fairly easy once you know when to try and catch it. Press the action button BEFORE the ball reaches you, then release it as it hits you/after it hits you.
Edited on 11 September 2017 - 05:41 AM
Dave-ee Jones #17
Posted 11 September 2017 - 07:28 AM
Update!
  • Fixed the bug where you can't pickup balls after someone gets hit.
  • Also added a feature to change the speed of the player's movement.

Update on my thoughts:
  • Thinking about making a 'config' file to store the player controls and settings (including player/ball speed) and sprites
  • Thinking about making other items for +1 in the fun area (bombs, blocks/walls, teleporters etc.)
  • Thinking about changing the 't_BALLS' metatable to 't_ITEMS' so I can turn the whole program into an API so you can create your own items (ooooooo!)
Edited on 11 September 2017 - 05:31 AM
nitrogenfingers #18
Posted 11 September 2017 - 08:07 AM
Because I made the player's teams changeable it means that players can change their team via the colour, which means that if I have a set scoreboard and people change the colours they aren't going to be scored..Can't think of a way to implement this without having each player have their own score.

If you can assume players can't change their team once a game starts, then you can loop through each player in the game, get a unique list of colours and use that for scorekeeping.


local players = { [1] = {team = colours.red}, [2] = {team = colours.blue}, [3] = {team = colours.red}, [4] = {team = colours.green}}
local team_scores = { }

local function createTeams()
  for _,player_entry in pairs(players) do
    team_scores[player_entry.team] = { score = 0 }
  end
end

local function displayScores()
  for team_color, team_score_value in pairs(team_scores) do
    term.setCursorPos([...])
    term.setTextColour(team_color)
    term.write(team_score)
  end
end

Incidentally I haven't tested any of these code snippets :P/> but hopefully you get the gist.
Dave-ee Jones #19
Posted 11 September 2017 - 08:47 AM
Because I made the player's teams changeable it means that players can change their team via the colour, which means that if I have a set scoreboard and people change the colours they aren't going to be scored..Can't think of a way to implement this without having each player have their own score.

If you can assume players can't change their team once a game starts, then you can loop through each player in the game, get a unique list of colours and use that for scorekeeping.


local players = { [1] = {team = colours.red}, [2] = {team = colours.blue}, [3] = {team = colours.red}, [4] = {team = colours.green}}
local team_scores = { }

local function createTeams()
  for _,player_entry in pairs(players) do
	team_scores[player_entry.team] = { score = 0 }
  end
end

local function displayScores()
  for team_color, team_score_value in pairs(team_scores) do
	term.setCursorPos([...])
	term.setTextColour(team_color)
	term.write(team_score)
  end
end

Incidentally I haven't tested any of these code snippets :P/> but hopefully you get the gist.

Yep, that's a good idea, I'll do that.

At this point this game may as well be yours, haha! Thanks for all the help :D/>
Dave-ee Jones #20
Posted 12 September 2017 - 04:38 AM
Update!

Scoreboard was added. It supports a (virtually) infinite amount of teams and is incremented by a player getting hit, adding a point to the team who hit the player. Check it out!

EDIT: Does anyone know why there is so much spacing in the OP? When I edit it it looks normal, but the space indenting is insane..
Edited on 12 September 2017 - 02:43 AM