Protobound Day 9

Protobound Day 9

Today I need to get the enemies wandering in the world and get started on the combat system.

The combat UI is becoming more solid in my head now, I can picture it and how it flows now. Soon. For now I am working out how the enemies will wander around and how agro is going to be handled. I think for brevity I will make it a simple circle around the enemy, when entered it triggers the chase state. If the player escapes chase range, return to start position and go back to wander state. I think an easy way to get the enemies working would be to rip off the NPC behavior and modify it. I could duplicate the whole NPC folder and modify the names of the scripts and scenes, then go in and strip out what I don't need so I can build on that.

A smarter man who allotted more time would rewrite the NPCs so the logic shared between enemies can be called from it's own sub-resource and keep it DRY. But hey, I don't have time to be smart, I got shit to do.

It begins with a Ctrl+D. There, I got a stripped down NPC. I need to get rid of a lot of the state, there will be no need to track last direction or anything like that, I don't see the use in having direction for what is essentially a blob of shadow that will just animate based on movement or idling. Maybe speed it up when chasing the player. I could have it be more complex but... why?

Queue the hard bass, time to get to work drawing the enemy overworld sprite really really fast which results in this monstrosity and crime against pixel art.

Delicious.

Quick or pretty, can't have both.

I seem to have a habit of using the comments in my code to think through what I am going to do. I also have a habit of ignoring notes I leave for myself and doing stuff in an entirely different way. Usually this is good, because it means I found a better way between writing that comment and implementation.

Handling it with area triggers.

I have to add checks and make sure the enemy doesn't go off chasing the NPCs or other enemies, of course. For now I just need to get the little buggers wandering around, then chase logic and on and on.

Don't mind me.

There's an enemy wandering about, now we work on the logic for wandering. It should have a larger wander radius than the NPCs and move a little faster. Also need to animate it. I think I will just change the animation speed, increasing it for movement and further increasing it for chasing.

Ohhh boy, just noticed my home folder has like 500mb left. Time to quickly clean up a little. There. 4.6gb. I really need to get a new SSD for this when I have the money.

The enemy now chases the player using a simple line of code.

velocity = position.direction_to(player.position) * chase_speed

player is the body that triggers the chase, so it's easy to get that variable. chase_speed is an export int that currently is double normal speed, still easily escaped by the player. Now to get it to return to it's starting point when it is escaped.

The enemy does return to it's starting point but it kinda can't seem to exactly hit the point. I need to make it +/-1 unity on x and y(verbose) or instancing a destination at the start_point that sets the enemy's state back to IDLE on collision. I did build the destination-based movement system, might as well use it.

Time for lunch and dog walk, then back to work adding the destination cast(as in fishing) for the return home.

Alright, the enemy will now return home. Though if their destination remains on the map and the player drags them to it they will stop and return to idle. I will fix this the cheap way with a check on the destination_reached making sure the enemy is not in chase mode.

Right, now that I got an enemy wandering the world I can start the combat stuff. First of all the player and party need HP and SP (current/max). The enemy needs to know it's party configuration as well... which will need some thinkin'.

I've made it so the overworld enemy holds a party of scenes that represent individual enemies. The party size can be 1-4 and each enemy is responsible for thier own stats.

When the player runs into an enemy, the enemy should open the battle screen which will set the state of the Game to battle and display a UI panel which I have to design now. Oh boy, my favorite part.

What fun. How I love designing UI.
Blocking it out.

I'm leaving space for status effects on the bottom of each, though I do not plan to add them in to the prototype because I didn't allow for them with my scheduling. If I somehow have time I will add them as Icons. Otherwise if I come back to this project someday it's got the space there, ready to go and all the reference material on where things go here on the blog as well as commented in the code.

For finding turn order I will need a sorting algorithm, wont I? Been a while since I needed one of these. First I need to get the speed of each participant.

Got speed?

Now I need to add them to the turn array based on their speed. I could construct this with a dict, which would be easier but heavier on resources (depending on how Arrays are implemented behind the scenes).

I could have it be an array of dictionaries, now that I think of it. This way I could more easily access the data required of the participants. I can put them into the array based on their speed stat, then load their information from it on their turn. I guess I'll try that, then work out the sorting algorithm based on it.

func set_turn_order(enemy_party, player_party):
	print("Setting up turn order")
	var unsorted_participants = []
	for enemy in enemy_party:
		#print(str(enemy_party[enemy]) + " Speed " + str(enemy_party[enemy].modified_stats["speed"]))
		var participant = {
			"node": enemy_party[enemy],
			"name": enemy_party[enemy].enemy_name,
			"speed": enemy_party[enemy].modified_stats["speed"],
			"stats": enemy_party[enemy].modified_stats
		}
		unsorted_participants.push_back(participant)
	for member in player_party:
		#print(str(player_party[member]) + " Speed " + str(player_party[member].get_node("Stats").modified_stats["speed"]))
		var participant = {
			"node": player_party[member],
			"name": player_party[member].character_name,
			"speed": player_party[member].get_node("Stats").modified_stats["speed"],
			"stats": player_party[member].get_node("Stats").modified_stats
		}
		unsorted_participants.push_back(participant)

	turn_order = unsorted_participants.sort_custom(self, "sort_by_speed")
	print("SORTED!")
	for participant in unsorted_participants:
		print(participant)
	
func sort_by_speed(a, b):
	return a["speed"] > b["speed"]

It'll do.

Yeah, that's sorted by speed.

If a participant is killed, they are removed from turn order. All we have to do is process the turn order. I am using the node datapoint as a unique identifier because enemy parties will consist of multiple enemies with the same name.

func battle():
	var currently_active = turn_order.pop_front()
	take_turn(currently_active)

This is how we handle turns with the turn_order array. Pop it off the front, pass it to the take_turn() function and inside of take_turn push it onto the back.

func take_turn(currently_active):
	#Take turn
	turn_order.push_back(currently_active)
	battle()
Until STACK OVERFLOW! WOOOOOO! The OS wins this battle.

Interesting hard bass mix.

Now all I have to do is all the rest. Haha...haaa...

So we need to know first who's taking a turn, a party member or enemy. Handle enemies with some basic AI, handle party members with a UI.

It works, mostly.

I do need to populate the UI properly with the correct data on battle start, I should probably be able to get that done by the end of the day. It's 4:22pm now so I had better get on it.

3 against 1, robot gonna get fucked up.

It's 4:54 PM now. I just got the UI to load the data correctly. I do have a bug where the player gets one extra turn per round but I think I know where that might be happening. Tomorrow I will fix it and get working on the logic for player and enemy turns, if I have time I will implement death and end of combat. If I manage that tomorrow I will move on to Save/Load on Wednesday.

Anyway, time to go make dinner now.

Cheers.