#define _POSIX_C_SOURCE 200809L #define _XOPEN_SOURCE_EXTENDED #include #include #include #include #include #include #include #include #include #include #include #include #include #define NOOBJ_OBJECT (struct object){.type = NOOBJ, .symbol = ' '} unsigned m_id = 0; static struct choice get_input(void) { struct choice c; start:; int res = getch(); assert(res != ERR); switch(res) { case 'w': c.a = MOVE; c.u.d = UP; break; case 'a': c.a = MOVE; c.u.d = LEFT; break; case 's': c.a = MOVE; c.u.d = DOWN; break; case 'd': c.a = MOVE; c.u.d = RIGHT; break; case 'f': c.a = SPAWN; break; case 'q': c.a = QUIT; break; case 'i': c.a = INVENTORY; break; case KEY_RESIZE: c.a = REDRAW; break; case KEY_UP: log_scroll(UP); goto start; case KEY_DOWN: log_scroll(DOWN); goto start; default: c.a = INVALID; } return c; } //adds xp to m, increases stats and opens a messagebox static void addxp(struct monster *m, unsigned xp) { m->xp += xp; if(m->xp >= m->level *100) { m->xp -= m->level*100; m->level++; m->dmg++; m->max_hp += 10; m->current_hp += 10; const char *str = "!You leveled up!"; messagebox(str, 1, strlen(str)); } } //inflicts src's damage to target, removes targets monster if hp goes to zero //gives src xp if target is killed static void attack(struct monster *src, struct field *target) { assert(target->has_monster); if(src->isplayer) logstr("%s hit %s for %d damage", src->name, target->m.name, src->dmg); if(src->dmg >= target->m.current_hp) { logstr("%s killed %s", src->name ,target->m.name); if(src->isplayer) { addxp(src, target->m.level*10); } else { messagebox("U done did dedden", 1, 0); exit(0); } darray_free(target->m.items); target->has_monster = false; } else { target->m.current_hp -= src->dmg; } } //returns true if adding was successful, otherwise false static bool additem(struct monster *m, struct item *it) { darray_append(m->items, *it); return true; } static void rmvitem(struct monster *m, size_t i) { darray_remove(m->items, i); } //called by itemselect, uses an item if its usable //equips it and does stat recalculation if its an equip static bool inventorycallback(struct monster *m, size_t i) { struct item *it = &darray_item(m->items,i); if(it->type == USE) { m->current_hp = MIN(m->max_hp, m->current_hp+it->u.u.restorehp); rmvitem(m, i); return true; } else if(it->type == EQUIP) { if(it->u.e.isequipped) { m->max_hp -= it->u.e.hp; m->current_hp = MAX(1, m->current_hp-it->u.e.hp); m->dmg -= it->u.e.dmg; } else { m->max_hp += it->u.e.hp; m->current_hp += it->u.e.hp; m->dmg += it->u.e.dmg; } it->u.e.isequipped = !it->u.e.isequipped; } return false; } static void inventory(struct monster *m) { itemselect(m, inventorycallback); } //moves the object in src to target if target is empty //puts an item in the inventory of src if src is a monster and target contains an item //TODO: player moves on target, create new map static struct point move_object(struct game *g, struct point src, struct point target) { if((game_at(g, target)->u->ground.type == NOOBJ || game_at(g, target)->u->ground.type == TARGET) && !game_at(g, target)->has_monster) { game_at(g, target)->has_monster = true; game_at(g, target)->m = game_at(g, src)->m; game_at(g, src)->has_monster = false; return target; } else if(game_at(g, target)->u->ground.type == ITEM && !game_at(g, target)->has_monster && game_at(g, src)->has_monster) { if(!additem(&game_at(g, src)->m, &game_at(g, target)->u->it)) { logstr("Inventory is full"); return src; } game_at(g, target)->has_monster = true; game_at(g, target)->m = game_at(g, src)->m; game_at(g, target)->u->ground = EMPTYFIELD; game_at(g, src)->has_monster = false; return target; } else { return src; } } static inline struct point attack_move(struct game *g, struct point src, struct point target) { assert(game_at(g, src)->has_monster); struct field *t = game_at(g, target); if(t->has_monster) { attack(&game_at(g, src)->m, t); } else { return move_object(g, src, target); } return src; } static void run_ai(struct game *g, struct point player) { darray(unsigned) checked = darray_new(); for(size_t y = 0; y < g->ys; y++) { for(size_t x = 0; x < g->xs; x++) { pointarr res; if(game_at(g, (struct point){y,x})->has_monster && !game_at(g, (struct point){y,x})->m.isplayer) { unsigned *id; darray_foreach(id, checked) { if(*id == game_at(g, (struct point){y,x})->m.id) goto next; } darray_append(checked, game_at(g, (struct point){y,x})->m.id); res = astar(g, (struct point){y,x}, player, AGGRO_RANGE); if(res.size >= 2) { attack_move(g, (struct point){y,x}, darray_item(res, res.size-2)); } else { struct point ps[4] = { {y, x-1}, {y, x+1}, {y-1, x}, {y+1, x} }; move_object(g, (struct point){y,x}, ps[randrange(0,3)]); } darray_free(res); } next:; } } darray_free(checked); } //creates game and spawns loot and enemies static struct game setup_game() { struct game g = make_maze(randrange(GAME_MIN_Y, GAME_MAX_Y), randrange(GAME_MIN_X, GAME_MAX_X)); game_at(&g, (struct point){1,1})->m = (struct monster) //TODO: keep player between levels { .symbol = 'P', .id = -1, .name = "Player", .max_hp = 20, .current_hp = 20, .dmg = 2, .level = 1, .xp = 0, .items = darray_new(), .isplayer = true }; game_at(&g, (struct point){1,1})->has_monster = true; size_t num_enemies = randrange(MIN_ENEMIES, MAX_ENEMIES); for(size_t i = 0; i < num_enemies; i++) { spawn_enemy(&g); } size_t num_items = randrange(MIN_LOOT, MAX_LOOT); for(size_t i = 0; i < num_items; i++) { spawn_item(&g); } return g; } void loop() { struct game g = setup_game(); struct point player = {1,1}; while(true) { charclear(); charprint("(づ。◕‿‿◕。)づ"); charprint("Name: %s", game_at(&g, player)->m.name); charprint("HP : %u/%u", game_at(&g, player)->m.current_hp, game_at(&g, player)->m.max_hp); charprint("DMG: %u", game_at(&g, player)->m.dmg); charprint("LVL: %u", game_at(&g, player)->m.level); charprint("XP : %u", game_at(&g, player)->m.xp); draw_map(&g, player); struct choice c = get_input(); switch(c.a) { case INVALID: logstr("Invalid input, try again"); continue; case QUIT: delete_game(&g); return; case MOVE:; struct point targetpos = relative(player, c.u.d); if(!in_bounds(&g, targetpos)) { logstr("Target not in bounds"); continue; } player = attack_move(&g, player, targetpos); if(game_at(&g, player)->u->ground.type == TARGET) { delete_game(&g); loop(); return; } run_ai(&g, player); break; case SPAWN: spawn_enemy(&g); break; case INVENTORY: inventory(&game_at(&g, player)->m); break; case REDRAW: setup(); printlog(); break; } } }