loop.c
Raw
1#define _POSIX_C_SOURCE 200809L
2#define _XOPEN_SOURCE_EXTENDED
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <assert.h>
7#include <stdbool.h>
8#include <unistd.h>
9
10#include <ncurses.h>
11
12#include <game.h>
13#include <helper.h>
14#include <tui.h>
15#include <algorithm.h>
16#include <config.h>
17#include <items.h>
18
19#define NOOBJ_OBJECT (struct object){.type = NOOBJ, .symbol = ' '}
20
21unsigned m_id = 0;
22
23static struct choice get_input(void)
24{
25 struct choice c;
26
27start:;
28 int res = getch();
29 assert(res != ERR);
30 switch(res)
31 {
32 case 'w':
33 c.a = MOVE;
34 c.u.d = UP;
35 break;
36 case 'a':
37 c.a = MOVE;
38 c.u.d = LEFT;
39 break;
40 case 's':
41 c.a = MOVE;
42 c.u.d = DOWN;
43 break;
44 case 'd':
45 c.a = MOVE;
46 c.u.d = RIGHT;
47 break;
48 case 'f':
49 c.a = SPAWN;
50 break;
51 case 'q':
52 c.a = QUIT;
53 break;
54 case 'i':
55 c.a = INVENTORY;
56 break;
57 case KEY_RESIZE:
58 c.a = REDRAW;
59 break;
60 case KEY_UP:
61 log_scroll(UP);
62 goto start;
63 case KEY_DOWN:
64 log_scroll(DOWN);
65 goto start;
66 default:
67 c.a = INVALID;
68 }
69
70 return c;
71}
72
73//adds xp to m, increases stats and opens a messagebox
74static void addxp(struct monster *m, unsigned xp)
75{
76 m->xp += xp;
77 if(m->xp >= m->level *100) {
78 m->xp -= m->level*100;
79 m->level++;
80 m->dmg++;
81 m->max_hp += 10;
82 m->current_hp += 10;
83 const char *str = "!You leveled up!";
84 messagebox(str, 1, strlen(str));
85 }
86}
87
88//inflicts src's damage to target, removes targets monster if hp goes to zero
89//gives src xp if target is killed
90static void attack(struct monster *src, struct field *target)
91{
92 assert(target->has_monster);
93 if(src->isplayer)
94 logstr("%s hit %s for %d damage", src->name, target->m.name, src->dmg);
95
96 if(src->dmg >= target->m.current_hp) {
97 logstr("%s killed %s", src->name ,target->m.name);
98
99 if(src->isplayer) {
100 addxp(src, target->m.level*10);
101 } else {
102 messagebox("U done did dedden", 1, 0);
103 exit(0);
104 }
105 darray_free(target->m.items);
106 target->has_monster = false;
107 } else {
108 target->m.current_hp -= src->dmg;
109 }
110}
111
112//returns true if adding was successful, otherwise false
113static bool additem(struct monster *m, struct item *it)
114{
115 darray_append(m->items, *it);
116 return true;
117}
118
119static void rmvitem(struct monster *m, size_t i)
120{
121 darray_remove(m->items, i);
122}
123
124//called by itemselect, uses an item if its usable
125//equips it and does stat recalculation if its an equip
126static bool inventorycallback(struct monster *m, size_t i)
127{
128 struct item *it = &darray_item(m->items,i);
129 if(it->type == USE) {
130 m->current_hp = MIN(m->max_hp, m->current_hp+it->u.u.restorehp);
131 rmvitem(m, i);
132 return true;
133 } else if(it->type == EQUIP) {
134 if(it->u.e.isequipped) {
135 m->max_hp -= it->u.e.hp;
136 m->current_hp = MAX(1, m->current_hp-it->u.e.hp);
137 m->dmg -= it->u.e.dmg;
138 } else {
139 m->max_hp += it->u.e.hp;
140 m->current_hp += it->u.e.hp;
141 m->dmg += it->u.e.dmg;
142 }
143
144 it->u.e.isequipped = !it->u.e.isequipped;
145 }
146 return false;
147}
148
149static void inventory(struct monster *m)
150{
151 itemselect(m, inventorycallback);
152}
153
154//moves the object in src to target if target is empty
155//puts an item in the inventory of src if src is a monster and target contains an item
156//TODO: player moves on target, create new map
157static struct point move_object(struct game *g, struct point src, struct point target)
158{
159 if((game_at(g, target)->u->ground.type == NOOBJ || game_at(g, target)->u->ground.type == TARGET) && !game_at(g, target)->has_monster) {
160 game_at(g, target)->has_monster = true;
161 game_at(g, target)->m = game_at(g, src)->m;
162 game_at(g, src)->has_monster = false;
163 return target;
164 } else if(game_at(g, target)->u->ground.type == ITEM && !game_at(g, target)->has_monster && game_at(g, src)->has_monster) {
165 if(!additem(&game_at(g, src)->m, &game_at(g, target)->u->it)) {
166 logstr("Inventory is full");
167 return src;
168 }
169 game_at(g, target)->has_monster = true;
170 game_at(g, target)->m = game_at(g, src)->m;
171 game_at(g, target)->u->ground = EMPTYFIELD;
172 game_at(g, src)->has_monster = false;
173 return target;
174 } else {
175 return src;
176 }
177}
178
179static inline struct point attack_move(struct game *g, struct point src, struct point target)
180{
181 assert(game_at(g, src)->has_monster);
182 struct field *t = game_at(g, target);
183
184 if(t->has_monster) {
185 attack(&game_at(g, src)->m, t);
186 } else {
187 return move_object(g, src, target);
188 }
189 return src;
190}
191
192static void run_ai(struct game *g, struct point player)
193{
194 darray(unsigned) checked = darray_new();
195 for(size_t y = 0; y < g->ys; y++)
196 {
197 for(size_t x = 0; x < g->xs; x++)
198 {
199 pointarr res;
200 if(game_at(g, (struct point){y,x})->has_monster && !game_at(g, (struct point){y,x})->m.isplayer) {
201 unsigned *id;
202 darray_foreach(id, checked)
203 {
204 if(*id == game_at(g, (struct point){y,x})->m.id)
205 goto next;
206 }
207 darray_append(checked, game_at(g, (struct point){y,x})->m.id);
208
209 if(insight(g, ((struct point){y,x}), player)) {
210 res = astar(g, (struct point){y,x}, player, 20);
211 if(res.size >= 2) {
212 attack_move(g, (struct point){y,x}, darray_item(res, res.size-2));
213 } else {
214 struct point ps[4] =
215 {
216 {y, x-1},
217 {y, x+1},
218 {y-1, x},
219 {y+1, x}
220 };
221 move_object(g, (struct point){y,x}, ps[randrange(0,3)]);
222 }
223 darray_free(res);
224 }
225
226 }
227 next:;
228 }
229 }
230 darray_free(checked);
231}
232
233//creates game and spawns loot and enemies
234static struct game setup_game()
235{
236 struct game g = make_maze(randrange(GAME_MIN_Y, GAME_MAX_Y), randrange(GAME_MIN_X, GAME_MAX_X));
237
238 game_at(&g, (struct point){1,1})->m = (struct monster) //TODO: keep player between levels
239 {
240 .symbol = 'P',
241 .id = -1,
242 .name = "Player",
243 .max_hp = 20,
244 .current_hp = 20,
245 .dmg = 2,
246 .level = 1,
247 .xp = 0,
248 .items = darray_new(),
249 .isplayer = true
250 };
251 game_at(&g, (struct point){1,1})->has_monster = true;
252
253 size_t num_enemies = randrange(MIN_ENEMIES, MAX_ENEMIES);
254 for(size_t i = 0; i < num_enemies; i++)
255 {
256 spawn_enemy(&g);
257 }
258
259 size_t num_items = randrange(MIN_LOOT, MAX_LOOT);
260 for(size_t i = 0; i < num_items; i++)
261 {
262 spawn_item(&g);
263 }
264
265 return g;
266}
267
268
269void loop()
270{
271 struct game g = setup_game();
272
273 struct point player = {1,1};
274
275 while(true)
276 {
277 charclear();
278 charprint("(づ。◕‿‿◕。)づ");
279 charprint("Name: %s", game_at(&g, player)->m.name);
280 charprint("HP : %u/%u", game_at(&g, player)->m.current_hp, game_at(&g, player)->m.max_hp);
281 charprint("DMG: %u", game_at(&g, player)->m.dmg);
282 charprint("LVL: %u", game_at(&g, player)->m.level);
283 charprint("XP : %u", game_at(&g, player)->m.xp);
284 draw_map(&g, player);
285
286 struct choice c = get_input();
287 switch(c.a)
288 {
289 case INVALID:
290 logstr("Invalid input, try again");
291 continue;
292 case QUIT:
293 delete_game(&g);
294 return;
295 case MOVE:;
296 struct point oldpos = player;
297 struct point targetpos = relative(player, c.u.d);
298
299 if(!in_bounds(&g, targetpos)) {
300 logstr("Target not in bounds");
301 continue;
302 }
303
304 player = attack_move(&g, player, targetpos);
305 if(game_at(&g, player)->u->ground.type == TARGET)
306 {
307 delete_game(&g);
308 loop();
309 return;
310 }
311 run_ai(&g, oldpos);
312 break;
313 case SPAWN:
314 spawn_enemy(&g);
315 break;
316 case INVENTORY:
317 inventory(&game_at(&g, player)->m);
318 break;
319 case REDRAW:
320 setup();
321 printlog();
322 break;
323 }
324 }
325}
326