summaryrefslogtreecommitdiff
path: root/game.ha
blob: c44ef89aab28b0283a24bc3d3837c16d98e08a90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
use fmt;
use gl::*;
use glm;
use mcproto;
use time;

type Gamemode = enum u8 {
	SURVIVAL = 0,
	CREATIVE = 1,
	ADVENTURE = 2,
	SPECTATOR = 3,
	COUNT,
};

fn strgamemode(gamemode: Gamemode) str = {
	switch (gamemode) {
	case Gamemode::SURVIVAL =>
		return "survival";
	case Gamemode::CREATIVE =>
		return "creative";
	case Gamemode::ADVENTURE =>
		return "adventure";
	case Gamemode::SPECTATOR =>
		return "spectator";
	case =>
		abort("unknown gamemode");
	};
};

let GAMEMODE = Gamemode::SURVIVAL;
let DIM_TYPE = 0z;
let VIEW_DISTANCE = 0i32;

let LOADING_READY = false;
let LOADING_RECEIVED_SPAWN_POSITION = false;

def TICK_INTERVAL = time::SECOND / 20;
def TICK_MAX_PER_FRAME = 40u64;

let TICK_EPOCH = time::instant { ... };
let TICK_REAL_TIME_COUNT = 0u64;
let TICK_COUNT = 0u64;

type DimType = struct {
	name: str,
	min_y: i8, // in chunks
	height: u8, // in chunks
};

let DIM_TYPES: []DimType = [];

fn game_init() void = {
	TICK_EPOCH = frame_timestamp();

	let out: []u8 = [];
	defer free(out);
	mcproto::encode_string(&out, "en_US");
	append(out, 40);
	mcproto::encode_varint(&out, 0);
	mcproto::encode_bool(&out, true);
	append(out, 0);
	mcproto::encode_varint(&out, 1);
	mcproto::encode_bool(&out, false);
	mcproto::encode_bool(&out, true);
	network_send(0x07, out);

	game_respawn();
};

fn game_respawn() void = {
	chunks_respawn();
};

fn game_despawn() void = {
	death_close();
	chunks_despawn();

	LOADING_READY = false;
	LOADING_RECEIVED_SPAWN_POSITION = false;

	player_despawn();
};

fn game_destroy() void = {
	game_despawn();
	VIEW_DISTANCE = 0;
	TICK_REAL_TIME_COUNT = 0;
	TICK_COUNT = 0;
};

fn game_frame() void = {
	if (!LOADING_READY && loading_is_ready()) {
		LOADING_READY = true;
	};

	death_frame();
	control_frame();

	const ts = frame_timestamp();
	const ticks = (time::diff(TICK_EPOCH, ts) / TICK_INTERVAL): u64;
	const frame_ticks = ticks - TICK_REAL_TIME_COUNT;
	const frame_ticks = if (frame_ticks < TICK_MAX_PER_FRAME)
		frame_ticks else TICK_MAX_PER_FRAME;
	TICK_REAL_TIME_COUNT = ticks;
	for (let i = 0u64; i < frame_ticks; i += 1) {
		TICK_COUNT += 1;
		game_tick();
	};

	render_chunks_frame();
};

fn game_tick() void = {
	player_tick();
};

fn loading_is_ready() bool = {
	if (!LOADING_RECEIVED_SPAWN_POSITION) {
		return false;
	};
	if (GAMEMODE == Gamemode::SPECTATOR || DEATH_SHOWN) {
		return true;
	};
	const min_y = CHUNKS_MIN_Y: f32 * 16.0;
	const max_y = min_y + CHUNKS_HEIGHT: f32 * 16.0 + 1.0;
	if (PLAYER_POS[1] < min_y || PLAYER_POS[1] >= max_y) {
		return true;
	};
	const player_chunk_pos = ChunkPos {
		x = PLAYER_POS[0]: i32 >> 4,
		z = PLAYER_POS[2]: i32 >> 4,
	};
	if (getchunk(player_chunk_pos) is *Chunk) {
		// TODO: query render status. infeasible at the moment due to
		// dumb chunk update order.
		return true;
	};
	return false;
};

const LAYER_GAME = Layer {
	blocks_input = &layer_game_blocks_input,
	input = &layer_game_input,
	is_opaque = &layer_game_is_opaque,
	render = &layer_game_render,
};

fn layer_game_blocks_input() bool = {
	return true;
};

fn layer_game_input(event: InputEvent) bool = {
	control_input(event);
	return true;
};

fn layer_game_is_opaque() bool = {
	return true;
};

fn layer_game_render() void = {
	if (CLIENT_STATE != ClientState::GAME) {
		return;
	};

	const fovy = 70.0f32;

	let (width, height) = drawable_size();
	glViewport(0, 0, width: i32, height: i32);

	let view = glm::m4_new_ident();
	glm::translate(&view, &({
		let neg_pos = PLAYER_POS;
		neg_pos = glm::v3_sub(&neg_pos, &[
			CHUNKS_POS.x: f32 * 16.0,
			0.0,
			CHUNKS_POS.z: f32 * 16.0,
		]);
		glm::v3_negate(&neg_pos);
		yield neg_pos;
	}));
	// the angle here should be negated, as the view transform is the
	// inverse of the camera transform, except for, contrary to mathematical
	// convention, Minecraft's "yaw" is positive-clockwise (as seen from
	// above), so we get two negations.
	glm::rotate(&view, glm::rad(PLAYER_YAW), &glm::v3_new(0.0, 1.0, 0.0));
	// then we need to rotate by 180°, since a yaw of 0 means the player is
	// looking towards the +Z direction, but forward in view space is
	// actually -Z.
	glm::scale(&view, &glm::v3_new(-1.0, 1.0, -1.0));
	// whether or not pitch goes against convention too is dependent on your
	// perspective, but since we've already rotated by 180° about Y, in our
	// case it does.
	glm::rotate(&view, glm::rad(PLAYER_PITCH), &glm::v3_new(1.0, 0.0, 0.0));

	glClearColor(0.033, 0.01, 1.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	let projection = glm::m4_new_zero();
	glm::perspective(&projection,
		glm::rad(fovy), width: f32 / height: f32,
		0.1, 4096.0,
	);

	const view_proj = glm::m4_mul(&projection, &view);

	glEnable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_BLEND);

	render_chunks_render(&view_proj);
};

const LAYER_GAME_WAITING = Layer {
	blocks_input = &layer_game_waiting_blocks_input,
	input = &layer_game_waiting_input,
	is_opaque = &layer_game_waiting_is_opaque,
	render = &layer_game_waiting_render,
};

fn layer_game_waiting_blocks_input() bool = {
	return CLIENT_STATE == ClientState::GAME && !LOADING_READY;
};

fn layer_game_waiting_input(event: InputEvent) bool = {
	return false;
};

fn layer_game_waiting_is_opaque() bool = {
	return CLIENT_STATE == ClientState::GAME && !LOADING_READY;
};

fn layer_game_waiting_render() void = {
	if (CLIENT_STATE != ClientState::GAME || LOADING_READY) {
		return;
	};

	render_wait_screen("Loading terrain...");
};