among-sus/main.c

1547 lines
42 KiB
C
Raw Normal View History

2020-10-17 21:10:38 +00:00
#include <arpa/inet.h>
2020-10-17 14:19:22 +00:00
#include <assert.h>
2020-10-17 21:10:38 +00:00
#include <ctype.h>
#include <errno.h>
2020-10-17 14:19:22 +00:00
#include <limits.h>
2020-10-17 21:10:38 +00:00
#include <netdb.h>
#include <netinet/in.h>
#include <stdbool.h>
2020-10-19 15:44:14 +00:00
#include <stdint.h>
2020-10-17 21:10:38 +00:00
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
2020-10-16 22:11:52 +00:00
#include <sys/types.h>
2020-10-17 21:10:38 +00:00
#include <sys/select.h>
2020-10-16 22:11:52 +00:00
#include <sys/socket.h>
#include <time.h>
2020-10-17 21:10:38 +00:00
#include <unistd.h>
2020-11-07 22:21:04 +00:00
#include <getopt.h>
2020-10-16 22:11:52 +00:00
2020-12-02 19:47:22 +00:00
#ifndef VERSION
#define VERSION "unknown"
#endif
2020-10-16 22:11:52 +00:00
#define NUM_PLAYERS 10
2020-10-16 23:55:27 +00:00
#define NUM_SHORT 6
#define NUM_LONG 2
2020-10-18 00:16:39 +00:00
#define NUM_CHATS 50
#define MIN_NAME 2
#define MAX_NAME 10
2020-10-16 22:11:52 +00:00
2020-10-22 17:52:23 +00:00
#ifndef MOVEMENT_NOTIFICATIONS
2020-10-18 21:03:27 +00:00
#define MOVEMENT_NOTIFICATIONS 1
2020-10-22 17:52:23 +00:00
#endif
2020-10-18 21:03:27 +00:00
2020-10-16 22:11:52 +00:00
enum game_stage {
STAGE_LOBBY,
STAGE_PLAYING,
STAGE_DISCUSS,
};
enum player_stage {
PLAYER_STAGE_NAME,
PLAYER_STAGE_LOBBY,
PLAYER_STAGE_MAIN,
PLAYER_STAGE_DISCUSS,
PLAYER_STAGE_WAITING,
2020-10-16 22:11:52 +00:00
};
2020-10-16 23:55:27 +00:00
enum player_task_short {
TASK_CAFE_TRASH,
TASK_CAFE_COFFEE,
TASK_CAFE_WIRES,
TASK_STORAGE_TRASH,
2020-10-19 13:43:10 +00:00
TASK_STORAGE_WIRES,
TASK_STORAGE_CLEAN,
2020-10-16 23:55:27 +00:00
TASK_ELECTRICAL_WIRES,
TASK_ELECTRICAL_BREAKERS,
TASK_ADMIN_WIRES,
2020-10-19 13:43:10 +00:00
TASK_ADMIN_CLEAN,
2020-10-16 23:55:27 +00:00
TASK_NAVIGATION_WIRES,
2020-10-19 13:43:10 +00:00
TASK_NAVIGATION_COURSE,
TASK_NAVIGATION_HEADING,
2020-10-16 23:55:27 +00:00
TASK_WEAPONS_WIRES,
2020-10-19 13:43:10 +00:00
TASK_WEAPONS_CALIBRATE,
2020-10-16 23:55:27 +00:00
TASK_SHIELDS_WIRES,
TASK_O2_WIRES,
TASK_O2_CLEAN,
2020-10-19 13:43:10 +00:00
TASK_OS_WATER,
2020-10-16 23:55:27 +00:00
TASK_MEDBAY_WIRES,
TASK_UPPER_CATALYZER,
TASK_LOWER_CATALYZER,
TASK_UPPER_COMPRESSION_COIL,
TASK_LOWER_COMPRESSION_COIL,
TASK_SHORT_COUNT,
2020-10-16 23:55:27 +00:00
};
2020-10-17 09:44:48 +00:00
const char short_task_descriptions[][45] = {
"Empty the cafeteria trash",
"Start the coffee maker in the cafeteria",
2020-10-18 15:37:35 +00:00
"Fix wiring in cafeteria",
"Empty the storage trash chute",
2020-10-19 13:43:10 +00:00
"Fix wiring in storage",
"Clean the floor in storage",
2020-10-17 09:44:48 +00:00
"Fix wiring in electrical",
"Reset breakers in electrical",
"Fix wiring in admin",
2020-10-19 13:43:10 +00:00
"Clean the floor in admin",
2020-10-17 09:44:48 +00:00
"Fix wiring in navigation",
2020-10-19 13:43:10 +00:00
"Adjust course in navigation",
"Check headings in navigation",
2020-10-17 09:44:48 +00:00
"Fix wiring in weapons",
2020-10-19 13:43:10 +00:00
"Calibrate targeting system in weapons",
2020-10-17 09:44:48 +00:00
"Fix wiring in shields",
"Fix wiring in o2",
2020-10-19 13:43:10 +00:00
"Clean oxygenator filter in o2",
"Water plants in o2",
2020-10-17 09:44:48 +00:00
"Fix wiring in medbay",
"Check catalyzer in upper engine",
"Check catalyzer in lower engine",
"Replace compression coil in upper engine",
"Replace compression coil in lower engine",
2020-10-17 09:44:48 +00:00
};
2020-10-16 23:55:27 +00:00
enum player_task_long {
2020-10-19 13:43:10 +00:00
TASK_SHIELDS_POWER,
TASK_WEAPONS_POWER,
TASK_NAV_LOG,
TASK_SHIELD_LOG,
TASK_REACTOR_FUEL,
TASK_POTATO,
2020-10-16 23:55:27 +00:00
TASK_LONG_COUNT
};
2020-10-19 13:43:10 +00:00
const char long_task_descriptions[][2][45] = {
{"Route power to defence in electrical", "Accept rerouted power in shields"},
{"Route power to attack in electrical", "Accept rerouted power in weapons"},
2020-10-29 20:25:52 +00:00
{"Download the latest navigation data", "Upload data in admin"},
{"Download the latest shields data", "Upload data in admin"},
2020-10-19 13:43:10 +00:00
{"Pick up nuclear fuel in storage", "Insert fuel into reactor"},
{"Pick up potato in cafeteria", "Plant the potato in o2"},
{"Get radio log from communications", "Deliver communications log to admin"},
2020-10-17 09:52:35 +00:00
};
2020-10-16 22:11:52 +00:00
enum player_location {
2020-10-17 09:29:47 +00:00
LOC_CAFETERIA,
2020-10-16 22:11:52 +00:00
LOC_REACTOR,
LOC_UPPER_ENGINE,
LOC_LOWER_ENGINE,
LOC_SECURITY,
LOC_MEDBAY,
LOC_ELECTRICAL,
LOC_STORAGE,
LOC_ADMIN,
LOC_COMMUNICATIONS,
LOC_O2,
LOC_WEAPONS,
LOC_SHIELDS,
LOC_NAVIGATION,
LOC_COUNT,
2020-10-16 22:11:52 +00:00
};
2020-10-17 10:24:15 +00:00
const char locations[][45] = {
[LOC_CAFETERIA] = "cafeteria",
[LOC_REACTOR] = "reactor",
[LOC_UPPER_ENGINE] = "upper",
[LOC_LOWER_ENGINE] = "lower",
[LOC_SECURITY] = "security",
[LOC_MEDBAY] = "medbay",
[LOC_ELECTRICAL] = "electrical",
[LOC_STORAGE] = "storage",
[LOC_ADMIN] = "admin",
[LOC_COMMUNICATIONS] = "communications",
[LOC_O2] = "o2",
[LOC_WEAPONS] = "weapons",
[LOC_SHIELDS] = "shields",
[LOC_NAVIGATION] = "navigation",
};
enum player_location doors[][10] = {
[LOC_CAFETERIA] = { LOC_MEDBAY, LOC_ADMIN, LOC_WEAPONS, LOC_COUNT },
[LOC_REACTOR] = { LOC_UPPER_ENGINE, LOC_SECURITY, LOC_LOWER_ENGINE, LOC_COUNT },
2020-10-21 14:05:49 +00:00
[LOC_UPPER_ENGINE] = { LOC_REACTOR, LOC_SECURITY, LOC_MEDBAY, LOC_COUNT },
2020-10-20 18:39:45 +00:00
[LOC_LOWER_ENGINE] = { LOC_REACTOR, LOC_SECURITY, LOC_ELECTRICAL, LOC_COUNT },
[LOC_SECURITY] = { LOC_UPPER_ENGINE, LOC_REACTOR, LOC_LOWER_ENGINE, LOC_COUNT },
[LOC_MEDBAY] = { LOC_UPPER_ENGINE, LOC_CAFETERIA, LOC_COUNT },
[LOC_ELECTRICAL] = { LOC_LOWER_ENGINE, LOC_STORAGE, LOC_COUNT },
[LOC_STORAGE] = { LOC_ELECTRICAL, LOC_ADMIN, LOC_COMMUNICATIONS, LOC_SHIELDS, LOC_COUNT },
[LOC_ADMIN] = { LOC_CAFETERIA, LOC_STORAGE, LOC_COUNT },
[LOC_COMMUNICATIONS] = { LOC_STORAGE, LOC_SHIELDS, LOC_COUNT },
[LOC_O2] = { LOC_SHIELDS, LOC_WEAPONS, LOC_NAVIGATION, LOC_COUNT },
[LOC_WEAPONS] = { LOC_CAFETERIA, LOC_O2, LOC_NAVIGATION, LOC_COUNT },
[LOC_SHIELDS] = { LOC_STORAGE, LOC_COMMUNICATIONS, LOC_O2, LOC_NAVIGATION, LOC_COUNT },
[LOC_NAVIGATION] = { LOC_WEAPONS, LOC_O2, LOC_SHIELDS, LOC_COUNT },
};
const char descriptions[][256] = {
[LOC_CAFETERIA] = "You are standing in the middle of the cafeteria, in the center there's an emergency button\n",
[LOC_REACTOR] = "You are in the reactor room, it seems to be running normally\n",
[LOC_UPPER_ENGINE] = "You are in a small room, mostly filled up by an engine.\n",
[LOC_LOWER_ENGINE] = "You are in a small room, mostly filled up by an engine.\n",
[LOC_SECURITY] = "You are in a small room filled with monitors, the monitors are displaying camera images showing an overview of the ship\n",
[LOC_MEDBAY] = "You are in a room with beds and a medical scanner.\n",
[LOC_ELECTRICAL] = "You are in a room filled with equipment racks. Some of them have wires sticking out of them\n",
[LOC_STORAGE] = "You are in a large room filled with boxes. One of the walls has a large door to the outside\n",
[LOC_ADMIN] = "You are in a nice carpeted room with a holographic map in the middle\n",
[LOC_COMMUNICATIONS] = "You are in a small room with what looks like radio equipment\n",
[LOC_O2] = "You are in a room with plants in terrariums and life support equipment\n",
[LOC_WEAPONS] = "You are in a circular room with a targeting system in the middle and a view of outer space\n",
[LOC_SHIELDS] = "You are in a circular room with glowing tubes and a control panel for the shields\n",
[LOC_NAVIGATION] = "You are all the way in the front of the ship in a room with the ship controls and a great view of space\n",
2020-10-17 10:24:15 +00:00
};
2020-11-07 16:54:13 +00:00
const char map[] =
"|\\----------------|--------------|----------------|--------------\\\n"
"| \\\n"
"| UPPER ENGINE CAFETERIA WEAPONS \\\n"
"| |- --------| | \\\n"
"|/--------| |--| MEDBAY | | \\\n"
" | | | | \\------\\\n"
"/---------| |-------\\ | |----------| | \\\n"
"| | | \\ |---| |------| | |\n"
"| \\ | | |\n"
"| REACTOR SECURITY | | ADMIN OFFICE | O2 NAVIGATION |\n"
"| | | | | |\n"
"| | | | |---| |----|-|----------| |\n"
"\\---------| |----------|------| | | /\n"
" | | | /------/\n"
"|\\--------| |--| | /\n"
"| | | |-- --| /\n"
"| LOWER ENGINE ELECTRICAL STORAGE | COMMS | SHIELDS /\n"
"| | | /\n"
"|/----------------|--------------|--------------|--------|-------/\n"
;
2020-10-18 15:35:24 +00:00
2020-11-07 22:21:04 +00:00
const char usage[] =
"Usage: among-sus [-p <port>] [-h]\n"
"among-sus:\tAmong Us, but it's a text adventure\n"
"\n"
"-p, \t\tSet port number\n"
"-h, \t\tDisplay this message\n"
;
enum player_state {
PLAYER_STATE_ALIVE,
PLAYER_STATE_VENT, // TODO: implement vents
PLAYER_STATE_DEAD,
PLAYER_STATE_FOUND,
PLAYER_STATE_EJECTED,
PLAYER_STATE_KICKED,
};
2020-10-16 22:11:52 +00:00
struct player {
int fd;
enum player_stage stage;
char name[MAX_NAME + 1];
2020-10-16 22:11:52 +00:00
int is_admin;
2020-10-21 17:58:46 +00:00
int is_impostor;
2020-10-16 22:11:52 +00:00
enum player_location location;
enum player_state state;
2020-10-16 22:11:52 +00:00
int has_cooldown;
2020-10-17 14:19:22 +00:00
int voted;
int votes;
2020-10-16 23:55:27 +00:00
enum player_task_short short_tasks[NUM_SHORT];
int short_tasks_done[NUM_SHORT];
enum player_task_long long_tasks[NUM_LONG];
int long_tasks_done[NUM_LONG];
2020-10-16 22:11:52 +00:00
};
struct gamestate {
enum game_stage stage;
int has_admin;
int players;
int is_reactor_meltdown;
2020-10-18 00:16:39 +00:00
int chats_left;
2020-10-18 13:51:36 +00:00
int skips;
unsigned int impostor_cooldown;
2020-10-16 22:11:52 +00:00
};
struct gamestate state;
struct player players[NUM_PLAYERS];
2020-10-18 13:51:36 +00:00
fd_set rfds, afds;
2020-10-16 22:11:52 +00:00
int
random_num(int upper_bound)
{
int ret;
do {
ret = rand();
} while (ret >= (RAND_MAX - RAND_MAX % upper_bound));
ret %= upper_bound;
return ret;
}
int
alive(struct player player)
{
return player.state == PLAYER_STATE_ALIVE || player.state == PLAYER_STATE_VENT;
}
2020-10-16 22:11:52 +00:00
void
broadcast(char* message, int notfd)
{
char buf[1024];
int pid;
printf("*> %s\n", message);
snprintf(buf, sizeof(buf), "%s\n", message);
2020-10-16 22:11:52 +00:00
for (pid = 0; pid < NUM_PLAYERS; pid++) {
if (players[pid].fd == -1 || players[pid].fd == notfd
|| players[pid].stage == PLAYER_STAGE_NAME
|| players[pid].stage == PLAYER_STAGE_WAITING)
2020-10-17 22:54:55 +00:00
continue;
2020-10-16 22:11:52 +00:00
write(players[pid].fd, buf, strlen(buf));
}
}
void
broadcast_ghosts(char* message, int notfd)
{
char buf[1024];
int pid;
printf("*> %s\n", message);
snprintf(buf, sizeof(buf), "%s\n", message);
for (pid = 0; pid < NUM_PLAYERS; pid++) {
2020-10-20 19:31:31 +00:00
if (players[pid].fd == -1
|| players[pid].fd == notfd
|| players[pid].stage == PLAYER_STAGE_NAME
|| players[pid].stage == PLAYER_STAGE_WAITING
|| alive(players[pid]))
continue;
write(players[pid].fd, buf, strlen(buf));
}
}
2020-10-16 22:11:52 +00:00
void
player_move(size_t pid, enum player_location location)
2020-10-16 22:11:52 +00:00
{
char buf[100];
2020-10-18 21:03:27 +00:00
enum player_location old_location = players[pid].location;
printf("Moving player %zu to %d\n", pid, location);
2020-10-16 22:11:52 +00:00
players[pid].location = location;
if (players[pid].has_cooldown != 0)
--players[pid].has_cooldown;
2020-10-16 22:11:52 +00:00
// body detection
for (size_t i = 0; i < NUM_PLAYERS; i++) {
if (players[i].location != players[pid].location || i == pid
|| players[i].fd == -1
|| players[i].state != PLAYER_STATE_DEAD
|| players[i].stage != PLAYER_STAGE_WAITING)
2020-10-16 22:11:52 +00:00
continue;
snprintf(buf, sizeof(buf), "you enter the room and see the body of [%s] laying on the floor\n", players[i].name);
write(players[pid].fd, buf, strlen(buf));
}
2020-10-18 21:03:27 +00:00
// Notify players you're moving
if (MOVEMENT_NOTIFICATIONS) {
for (size_t i = 0; i < NUM_PLAYERS; i++) {
if (players[i].fd == -1 || !alive(players[i])
|| !alive(players[pid]) || i == pid)
2020-10-18 21:03:27 +00:00
continue;
if (players[i].location == players[pid].location) {
snprintf(buf, sizeof(buf), "[%s] just walked into the room\n", players[pid].name);
write(players[i].fd, buf, strlen(buf));
}
if (players[i].location == old_location) {
snprintf(buf, sizeof(buf), "[%s] just left the room\n", players[pid].name);
write(players[i].fd, buf, strlen(buf));
}
}
}
}
void
end_game()
{
char buf[100];
broadcast("------------------------", -1);
broadcast("The game has ended, returning to lobby", -1);
state.stage = STAGE_LOBBY;
2020-10-16 22:11:52 +00:00
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].fd == -1)
2020-10-16 22:11:52 +00:00
continue;
if (players[i].stage == PLAYER_STAGE_WAITING) {
snprintf(buf, sizeof(buf), "Game ended, sending you to lobby.\n\a");
write(players[i].fd, buf, strlen(buf));
snprintf(buf, sizeof(buf), "[%s] has joined the lobby.", players[i].name);
broadcast(buf, players[i].fd);
}
players[i].stage = PLAYER_STAGE_LOBBY;
}
}
2020-10-16 22:11:52 +00:00
2020-10-18 21:14:54 +00:00
int
check_win_condition(void)
{
2020-10-18 13:51:36 +00:00
char buf[100];
size_t nalive = 0, iid = 0, tasks = 1;
2020-10-18 13:51:36 +00:00
2020-10-18 15:37:35 +00:00
for (size_t i = 0; i < NUM_PLAYERS; i++) {
2020-10-21 17:58:46 +00:00
if (players[i].fd != -1 && players[i].is_impostor)
2020-10-18 13:51:36 +00:00
iid = i;
2020-10-21 17:58:46 +00:00
if (players[i].is_impostor == 1
&& !alive(players[i])) {
2020-10-29 20:26:59 +00:00
broadcast("The crew won, the impostor died", -1);
end_game();
2020-10-18 21:14:54 +00:00
return 1;
}
2020-10-18 13:51:36 +00:00
if (players[i].fd != -1 && alive(players[i])
&& players[i].is_impostor == 0
&& players[i].stage != PLAYER_STAGE_WAITING)
nalive++;
2020-10-18 15:37:35 +00:00
if (players[i].fd != -1 && !players[i].is_impostor
&& players[i].stage != PLAYER_STAGE_WAITING) {
2020-10-18 15:37:35 +00:00
for (size_t j = 0; j < NUM_SHORT; j++) {
if (!players[i].short_tasks_done[j]) {
tasks = 0;
break;
}
}
for (size_t j = 0; j < NUM_LONG; j++) {
2020-10-19 13:43:10 +00:00
if (players[i].long_tasks_done[j] != 2) {
2020-10-18 15:37:35 +00:00
tasks = 0;
break;
}
}
}
}
if (tasks == 1) {
2020-10-29 20:26:59 +00:00
broadcast("The crew won, all tasks completed", -1);
2020-10-18 15:37:35 +00:00
end_game();
2020-10-18 21:14:54 +00:00
return 1;
}
if (nalive == 1) {
2020-12-02 05:11:16 +00:00
broadcast("The impostor is alone with the last crewmate and murders them", -1);
2020-10-21 17:58:46 +00:00
snprintf(buf, sizeof(buf), "The impostor was [%s] all along...", players[iid].name);
2020-10-18 13:51:36 +00:00
broadcast(buf, -1);
end_game();
2020-10-18 21:14:54 +00:00
return 1;
2020-10-16 22:11:52 +00:00
}
2020-10-18 21:14:54 +00:00
return 0;
2020-10-16 22:11:52 +00:00
}
2020-10-17 10:24:15 +00:00
void
task_completed(size_t pid, size_t task_id, int long_task)
2020-10-17 10:24:15 +00:00
{
// Mark task completed for player
if (!long_task) {
for (size_t i = 0; i < NUM_SHORT; i++) {
2020-10-17 10:24:15 +00:00
if (players[pid].short_tasks[i] == task_id) {
players[pid].short_tasks_done[i] = 1;
}
}
} else {
for(size_t i = 0; i < NUM_LONG; i++) {
2020-10-17 10:24:15 +00:00
if (players[pid].long_tasks[i] == task_id) {
2020-10-19 13:43:10 +00:00
players[pid].long_tasks_done[i]++;
2020-10-17 10:24:15 +00:00
}
}
}
check_win_condition();
2020-10-17 10:24:15 +00:00
}
2020-10-20 18:39:45 +00:00
void
list_rooms_with_players(size_t pid) {
int count[LOC_COUNT] = {0};
char buf[100];
for (size_t i = 0; i < NUM_PLAYERS; i++) {
if (players[i].fd != -1 && alive(players[i]))
count[players[i].location]++;
}
for (int i=0;i<LOC_COUNT;i++) {
if (count[i] > 0) {
snprintf(buf, sizeof(buf), " * There are %d players in %s\n",
count[i], locations[i]);
write(players[pid].fd, buf, strlen(buf));
}
}
}
2020-10-16 23:55:27 +00:00
void
player_list_tasks(size_t pid)
2020-10-16 23:55:27 +00:00
{
char buf[100];
2020-10-19 13:43:10 +00:00
int task_desc;
int done = 1;
2020-10-16 23:55:27 +00:00
for (size_t i = 0; i < TASK_SHORT_COUNT; i++) {
for (size_t j = 0; j < NUM_SHORT; j++) {
2020-10-16 23:55:27 +00:00
if(players[pid].short_tasks[j] == i) {
const char *cm;
2020-10-17 00:03:24 +00:00
if(players[pid].short_tasks_done[j]) {
cm = "*";
2020-10-17 00:03:24 +00:00
} else {
cm = " ";
done = 0;
2020-10-17 00:03:24 +00:00
}
snprintf(buf, sizeof(buf), " [%s] %s\n", cm,
short_task_descriptions[i]);
2020-10-16 23:55:27 +00:00
write(players[pid].fd, buf, strlen(buf));
}
}
}
for (size_t i = 0; i < TASK_LONG_COUNT; i++) {
for (size_t j = 0; j < NUM_LONG; j++) {
2020-10-17 09:52:35 +00:00
if(players[pid].long_tasks[j] == i) {
const char *cm;
2020-10-19 13:43:10 +00:00
if(players[pid].long_tasks_done[j] == 2) {
cm = "*";
2020-10-19 13:43:10 +00:00
task_desc = 0;
} else if(players[pid].long_tasks_done[j] == 1) {
cm = "-";
task_desc = 1;
done = 0;
2020-10-17 09:52:35 +00:00
} else {
cm = " ";
2020-10-19 13:43:10 +00:00
task_desc = 0;
done = 0;
2020-10-17 09:52:35 +00:00
}
snprintf(buf, sizeof(buf), " [%s] %s\n", cm,
2020-10-19 13:43:10 +00:00
long_task_descriptions[i][task_desc]);
2020-10-17 09:52:35 +00:00
write(players[pid].fd, buf, strlen(buf));
}
}
}
if (done) {
snprintf(buf, sizeof(buf), "All your tasks are completed!\n# ");
} else {
snprintf(buf, sizeof(buf), "Complete the tasks by typing the full task name in the correct location\n# ");
}
2020-10-17 10:24:15 +00:00
write(players[pid].fd, buf, strlen(buf));
2020-10-16 23:55:27 +00:00
}
bool
player_kill(size_t pid, size_t tid)
2020-10-16 22:11:52 +00:00
{
char buf[100];
if(players[pid].location != players[tid].location
|| players[tid].is_impostor
|| players[tid].stage != PLAYER_STAGE_MAIN)
return false;
2020-10-16 22:11:52 +00:00
// so sad
players[tid].state = PLAYER_STATE_DEAD;
2020-10-16 22:11:52 +00:00
// less murdering, reset by movement
players[pid].has_cooldown = state.impostor_cooldown;
2020-10-19 17:41:48 +00:00
2020-10-16 22:11:52 +00:00
// notify player of their recent death
2020-10-21 17:58:46 +00:00
snprintf(buf, sizeof(buf), "It turns out %s is the impostor, sadly the way you know is that you died.\n",
players[pid].name);
2020-10-16 22:11:52 +00:00
write(players[tid].fd, buf, strlen(buf));
// notify bystanders
for (size_t i = 0; i < NUM_PLAYERS; i++) {
if (i == pid || players[i].fd == -1 || !alive(players[i])
|| players[i].location != players[pid].location
|| players[i].stage != PLAYER_STAGE_MAIN)
2020-10-16 22:11:52 +00:00
continue;
snprintf(buf, sizeof(buf), "someone killed [%s] while you were in the room\n",
players[tid].name);
2020-10-16 22:11:52 +00:00
write(players[i].fd, buf, strlen(buf));
}
2020-10-19 17:41:48 +00:00
check_win_condition();
return true;
2020-10-16 22:11:52 +00:00
}
void
start_discussion(size_t pid, size_t bid)
2020-10-16 22:11:52 +00:00
{
2020-10-16 23:55:27 +00:00
char buf[100];
2020-10-16 22:11:52 +00:00
state.stage = STAGE_DISCUSS;
2020-10-18 13:51:36 +00:00
state.skips = 0;
2020-10-16 22:11:52 +00:00
2020-10-18 13:51:36 +00:00
// switch everyone to the discussion state and mark bodies found
2020-10-16 22:11:52 +00:00
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].fd == -1
|| players[i].stage != PLAYER_STAGE_MAIN)
2020-10-16 22:11:52 +00:00
continue;
players[i].stage = PLAYER_STAGE_DISCUSS;
players[i].voted = 0;
players[i].votes = 0;
2020-10-16 22:11:52 +00:00
}
2020-10-18 00:16:39 +00:00
broadcast("------------------------", -1);
2020-10-16 22:11:52 +00:00
// Inform everyone
if (bid == SIZE_MAX) {
2020-10-16 22:11:52 +00:00
// Emergency button was pressed
snprintf(buf, sizeof(buf), "\nAn emergency meeting was called by [%s]", players[pid].name);
2020-10-16 22:11:52 +00:00
} else {
// Body was reported
snprintf(buf, sizeof(buf), "\nThe body of [%s] was found by [%s]", players[bid].name, players[pid].name);
players[bid].state = PLAYER_STATE_FOUND;
2020-10-16 23:55:27 +00:00
}
broadcast(buf, -1);
// List the state of the players
broadcast("Players:", -1);
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].fd == -1
|| players[i].stage != PLAYER_STAGE_DISCUSS)
2020-10-16 23:55:27 +00:00
continue;
switch (players[i].state) {
case PLAYER_STATE_ALIVE:
snprintf(buf, sizeof(buf), "* %d [%s]", i, players[i].name);
break;
case PLAYER_STATE_DEAD:
snprintf(buf, sizeof(buf), "* %d [%s] (dead)", i, players[i].name);
break;
case PLAYER_STATE_FOUND:
snprintf(buf, sizeof(buf), "* %d [%s] (dead, reported)", i, players[i].name);
break;
case PLAYER_STATE_VENT:
case PLAYER_STATE_EJECTED:
case PLAYER_STATE_KICKED:
continue;
2020-10-16 23:55:27 +00:00
}
broadcast(buf, -1);
2020-10-16 22:11:52 +00:00
}
2020-10-18 00:16:39 +00:00
// Inform people of the chat limit
snprintf(buf, sizeof(buf), "Discuss, there are %d messages left", NUM_CHATS);
2020-10-18 00:16:39 +00:00
state.chats_left = NUM_CHATS;
broadcast(buf, -1);
2020-10-16 22:11:52 +00:00
}
2020-10-17 21:25:39 +00:00
void
back_to_playing()
{
state.stage = STAGE_PLAYING;
// switch everyone to the playing state
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].fd == -1
|| players[i].stage != PLAYER_STAGE_DISCUSS)
2020-10-17 21:25:39 +00:00
continue;
players[i].stage = PLAYER_STAGE_MAIN;
players[i].location = LOC_CAFETERIA;
2020-10-17 21:25:39 +00:00
}
broadcast("-- Voting has ended, back to the ship --\n\n# ", -1);
}
2020-10-16 22:11:52 +00:00
void
discussion(size_t pid, char *input)
2020-10-16 22:11:52 +00:00
{
2020-10-16 23:55:27 +00:00
char buf[300];
intmax_t vote = 0, max_votes = 0, tie = 0, winner = -1, hasvalidchar = 0;
for (size_t i = 0; i < strlen(input); i++) {
if (!isprint(input[i]))
input[i] = '\0';
else if (!isspace(input[i]))
hasvalidchar = 1;
}
2020-10-17 14:19:22 +00:00
if (input[0] == '/' && input[1] != '/') {
if ((strncmp(input, "/vote ", 6) == 0
|| strncmp(input, "/yeet ", 6) == 0
|| strncmp(input, "/skip", 6) == 0)
&& alive(players[pid]) ) {
2020-10-17 14:19:22 +00:00
if (players[pid].voted) {
snprintf(buf, sizeof(buf), "You can only vote once\n");
2020-10-17 14:19:22 +00:00
write(players[pid].fd, buf, strlen(buf));
return;
}
2020-10-18 13:51:36 +00:00
if (input[1] == 's') {
vote = -1;
} else {
char *endptr = NULL;
2020-10-19 16:28:48 +00:00
vote = strtol(&input[6], &endptr, 10);
2020-10-18 13:51:36 +00:00
if (!endptr || endptr[0] != '\0') {
snprintf(buf, sizeof(buf), "Invalid vote, not an integer\n");
write(players[pid].fd, buf, strlen(buf));
return;
}
}
2020-10-17 14:19:22 +00:00
2020-12-02 05:10:24 +00:00
if (vote != -1 && (vote < -1 || vote > NUM_PLAYERS-1
|| players[vote].fd == -1
|| players[vote].stage != PLAYER_STAGE_DISCUSS)) {
snprintf(buf, sizeof(buf), "Invalid vote, no such player\n");
2020-10-17 14:19:22 +00:00
write(players[pid].fd, buf, strlen(buf));
return;
}
if (!alive(players[vote])) {
snprintf(buf, sizeof(buf), "Invalid vote, that person is dead\n");
write(players[pid].fd, buf, strlen(buf));
return;
}
if (vote == -1) {
printf("[%s] voted to skip\n", players[pid].name);
} else {
printf("[%s] voted for %jd\n", players[pid].name, vote);
}
2020-10-17 14:19:22 +00:00
players[pid].voted = 1;
2020-10-18 13:51:36 +00:00
if (vote == -1) {
state.skips++;
} else {
players[vote].votes++;
}
check_votes:
2020-10-17 14:19:22 +00:00
// Check if voting is complete
for (size_t i = 0; i < NUM_PLAYERS; i++) {
if(players[i].fd != -1 && players[i].voted == 0
&& alive(players[i])
&& players[i].stage == PLAYER_STAGE_DISCUSS) {
2020-10-17 14:19:22 +00:00
printf("No vote from [%s] yet\n", players[i].name);
goto not_yet;
}
}
printf("Voting complete\n");
// Count votes
2020-10-18 13:51:36 +00:00
max_votes = state.skips;
for (size_t i = 0; i < NUM_PLAYERS; i++) {
if(players[i].fd == -1 || players[i].stage != PLAYER_STAGE_DISCUSS)
2020-10-17 14:19:22 +00:00
continue;
if(players[i].votes > max_votes){
max_votes = players[i].votes;
tie = 0;
winner = (intmax_t)i;
2020-10-17 14:19:22 +00:00
continue;
}
if(players[i].votes == max_votes) {
tie = 1;
}
}
printf("Vote winner: %jd\n", winner);
2020-10-17 14:19:22 +00:00
if (tie) {
broadcast("The voting ended in a tie", -1);
2020-10-17 21:25:39 +00:00
back_to_playing();
return;
2020-10-18 13:51:36 +00:00
} else if (winner == -1) {
snprintf(buf, sizeof(buf), "The crew voted to skip\n");
broadcast(buf, -1);
back_to_playing();
return;
2020-10-17 14:19:22 +00:00
} else {
snprintf(buf, sizeof(buf), "The crew voted to eject [%s]\n", players[winner].name);
2020-10-17 14:19:22 +00:00
broadcast(buf, -1);
}
// dramatic pause
for(int i=0;i<5;i++) {
sleep(1);
broadcast(".", -1);
}
players[winner].state = PLAYER_STATE_EJECTED;
2020-10-21 17:58:46 +00:00
if (players[winner].is_impostor) {
snprintf(buf, sizeof(buf), "It turns out [%s] was an impostor", players[winner].name);
2020-10-17 14:19:22 +00:00
broadcast(buf, -1);
} else {
2020-10-21 17:58:46 +00:00
snprintf(buf, sizeof(buf), "Sadly, [%s] was not an impostor", players[winner].name);
2020-10-17 14:19:22 +00:00
broadcast(buf, -1);
}
2020-10-18 21:14:54 +00:00
if(!check_win_condition()) {
back_to_playing();
}
return;
2020-10-17 14:19:22 +00:00
not_yet:
broadcast("A vote has been cast", -1);
2020-10-18 13:51:36 +00:00
} else if (strncmp(input, "/help", 6) == 0) {
snprintf(buf, sizeof(buf), "Commands: /vote [id], /skip, /list\n");
write(players[pid].fd, buf, strlen(buf));
} else if (strncmp(input, "/list", 6) == 0) {
for(int i=0; i<NUM_PLAYERS;i++) {
if (players[i].fd == -1
|| players[i].stage != PLAYER_STAGE_DISCUSS)
2020-10-18 13:51:36 +00:00
continue;
if (alive(players[i]) && players[i].voted) {
2020-10-18 13:51:36 +00:00
snprintf(buf, sizeof(buf), "* %d [%s] (voted) \n", i, players[i].name);
} else if (alive(players[i])) {
2020-10-18 13:51:36 +00:00
snprintf(buf, sizeof(buf), "* %d [%s]\n", i, players[i].name);
} else {
snprintf(buf, sizeof(buf), "* %d [%s] (dead)\n", i, players[i].name);
}
write(players[pid].fd, buf, strlen(buf));
}
snprintf(buf, sizeof(buf), "Commands: /vote [id], /skip, /list\n");
write(players[pid].fd, buf, strlen(buf));
} else if (strncmp(input, "/kick", 5) == 0) {
if (!players[pid].is_admin) {
snprintf(buf, sizeof(buf), "You have no kicking permission\n");
write(players[pid].fd, buf, strlen(buf));
}
char *endptr = NULL;
2020-10-19 18:39:27 +00:00
vote = strtol(&input[6], &endptr, 10);
2020-10-18 13:51:36 +00:00
if (!endptr || endptr[0] != '\0') {
snprintf(buf, sizeof(buf), "Invalid kick, not an integer\n");
write(players[pid].fd, buf, strlen(buf));
return;
}
snprintf(buf, sizeof(buf), "Admin kicked [%s]\n", players[vote].name);
broadcast(buf, -1);
2020-10-19 17:41:48 +00:00
2020-10-18 13:51:36 +00:00
close(players[vote].fd);
FD_CLR(players[vote].fd, &afds);
players[vote].fd = -1;
players[vote].state = PLAYER_STATE_KICKED;
2020-10-18 13:51:36 +00:00
goto check_votes;
2020-10-20 19:31:31 +00:00
} else if (strncmp(input, "/me ", 4) == 0) {
2020-10-22 18:58:00 +00:00
if (state.chats_left > 0 && alive(players[pid])) {
snprintf(buf, sizeof(buf), "(%d) * [%s] %s", state.chats_left, players[pid].name, &input[4]);
broadcast(buf, -1);
state.chats_left--;
2020-10-22 18:58:00 +00:00
} else if (alive(players[pid])) {
snprintf(buf, sizeof(buf), "No chats left, you can only vote now\n");
write(players[pid].fd, buf, strlen(buf));
return;
} else {
snprintf(buf, sizeof(buf), "(dead) * [%s] %s", players[pid].name, &input[4]);
broadcast_ghosts(buf, -1);
}
2020-10-20 19:31:31 +00:00
} else if (strncmp(input, "/shrug", 6) == 0) {
2020-10-22 18:58:00 +00:00
if (state.chats_left > 0 && alive(players[pid])) {
snprintf(buf, sizeof(buf), "(%d) [%s]: ¯\\_(ツ)_/¯", state.chats_left, players[pid].name);
broadcast(buf, -1);
state.chats_left--;
2020-10-22 18:58:00 +00:00
} else if (alive(players[pid])) {
snprintf(buf, sizeof(buf), "No chats left, you can only vote now\n");
write(players[pid].fd, buf, strlen(buf));
return;
} else {
snprintf(buf, sizeof(buf), "(dead) [%s]: ¯\\_(ツ)_/¯", players[pid].name);
broadcast_ghosts(buf, -1);
}
} else {
snprintf(buf, sizeof(buf), "Invalid command\n");
write(players[pid].fd, buf, strlen(buf));
}
} else if (input[0] == '/') {
if (state.chats_left == 0 && alive(players[pid])) {
snprintf(buf, sizeof(buf), "No chats left, you can only vote now\n");
write(players[pid].fd, buf, strlen(buf));
return;
2020-10-16 22:11:52 +00:00
}
if (alive(players[pid])) {
snprintf(buf, sizeof(buf), "(%d) [%s]: %s", state.chats_left, players[pid].name, &input[1]);
broadcast(buf, -1);
state.chats_left--;
} else {
snprintf(buf, sizeof(buf), "(dead) [%s]: %s", players[pid].name, &input[1]);
broadcast_ghosts(buf, -1);
}
} else if (hasvalidchar) {
2020-10-22 18:58:00 +00:00
if (state.chats_left <= 0 && alive(players[pid])) {
snprintf(buf, sizeof(buf), "No chats left, you can only vote now\n");
2020-10-18 00:16:39 +00:00
write(players[pid].fd, buf, strlen(buf));
return;
}
if (alive(players[pid])) {
snprintf(buf, sizeof(buf), "(%d) [%s]: %s", state.chats_left, players[pid].name, input);
broadcast(buf, -1);
state.chats_left--;
} else {
snprintf(buf, sizeof(buf), "(dead) [%s]: %s", players[pid].name, input);
broadcast_ghosts(buf, -1);
}
2020-10-16 22:11:52 +00:00
}
}
void
adventure(size_t pid, char *input)
2020-10-16 22:11:52 +00:00
{
char buf[1024];
const char *location;
size_t task_id;
2020-10-17 10:24:15 +00:00
int task_is_long;
2020-10-18 13:51:36 +00:00
if (input[0] == 'e' || strncmp(input, "ls", 3) == 0) {
enum player_location loc = players[pid].location;
strcpy(buf, "you can move to: ");
assert(doors[loc][0] != LOC_COUNT);
strncat(buf, locations[doors[loc][0]], sizeof(buf) - 1);
for (size_t i = 1; doors[loc][i] != LOC_COUNT; i++) {
strncat(strncat(buf, ", ", sizeof(buf) - 1),
locations[doors[loc][i]], sizeof(buf) - 1);
}
strncat(buf, "\n", sizeof(buf) - 1);
location = descriptions[loc];
if (loc == LOC_REACTOR && state.is_reactor_meltdown) {
location = "You are in the reactor room, there are red warning lights on and a siren is going off.\n";
2020-10-16 22:11:52 +00:00
}
write(players[pid].fd, location, strlen(location));
write(players[pid].fd, buf, strlen(buf));
for (size_t i = 0; i < NUM_PLAYERS; i++) {
if (players[i].location != players[pid].location
|| players[i].fd == -1 || i == pid
|| players[i].stage != PLAYER_STAGE_MAIN)
2020-10-16 22:11:52 +00:00
continue;
switch (players[i].state) {
case PLAYER_STATE_ALIVE:
snprintf(buf, sizeof(buf),
"you also see %s in the room with you\n",
players[i].name);
break;
case PLAYER_STATE_DEAD:
snprintf(buf, sizeof(buf),
"you also see %s's corpse in the room with you\n",
players[i].name);
break;
case PLAYER_STATE_FOUND:
snprintf(buf, sizeof(buf),
"you also see %s's reported corpse in the room with you\n",
players[i].name);
break;
case PLAYER_STATE_VENT:
case PLAYER_STATE_EJECTED:
case PLAYER_STATE_KICKED:
buf[0] = '\0';
}
write(players[pid].fd, buf, strlen(buf));
2020-10-16 22:11:52 +00:00
}
snprintf(buf, sizeof(buf), "# ");
2020-10-18 13:51:36 +00:00
} else if (strncmp(input, "go ", 3) == 0 || strncmp(input, "cd ", 3) == 0) {
enum player_location new;
for (new = 0; new < LOC_COUNT; new++) {
if (strcmp(locations[new], &input[3]) == 0) {
break;
}
}
if (new == LOC_COUNT) {
snprintf(buf, sizeof(buf), "INVALID MOVEMENT\n# ");
} else {
for (size_t i = 0; doors[players[pid].location][i] != LOC_COUNT; i++) {
if (doors[players[pid].location][i] == new) {
player_move(pid, new);
snprintf(buf, sizeof(buf),
"successfully moved\n# ");
new = LOC_COUNT;
break;
}
}
if (new != LOC_COUNT) {
snprintf(buf, sizeof(buf), "INVALID MOVEMENT\n# ");
}
2020-10-16 22:11:52 +00:00
}
} else if (strcmp(input, "murder crewmate") == 0) {
2020-10-21 17:58:46 +00:00
if (players[pid].is_impostor == 0) {
snprintf(buf, sizeof(buf), "you might dislike them, but you can't kill them without weapon\n# ");
2020-10-16 22:11:52 +00:00
} else if (players[pid].has_cooldown) {
snprintf(buf, sizeof(buf), "you can't kill that quickly\n# ");
2020-10-16 22:11:52 +00:00
} else {
snprintf(buf, sizeof(buf), "no one to kill here\n# ");
for (size_t i = 0; i < NUM_PLAYERS; i++) {
if (players[i].location != players[pid].location
|| i == pid || players[i].fd == -1
|| !alive(players[i])
|| players[i].stage != PLAYER_STAGE_MAIN)
2020-10-16 22:11:52 +00:00
continue;
// TODO: kill more randomly
if (player_kill(pid, i))
snprintf(buf, sizeof(buf), "you draw your weapon and brutally murder %s\n# ",
players[i].name);
2020-10-16 22:11:52 +00:00
break;
}
}
} else if (strcmp(input, "report") == 0) {
for(size_t i = 0; i < NUM_PLAYERS; i++) {
if (players[i].location != players[pid].location
|| i == pid || players[i].fd == -1
|| players[i].state != PLAYER_STATE_DEAD
|| !alive(players[pid]))
2020-10-16 22:11:52 +00:00
continue;
start_discussion(pid, i);
return;
}
snprintf(buf, sizeof(buf), "Nothing to report here\n# ");
} else if (strcmp(input, "press emergency button") == 0) {
2020-10-17 22:51:22 +00:00
if (players[pid].location != LOC_CAFETERIA) {
snprintf(buf, sizeof(buf), "You can't do that here\n# ");
} else if (!alive(players[pid])) {
snprintf(buf, sizeof(buf), "Ghosts can't call emergencies\n# ");
2020-10-17 22:51:22 +00:00
} else {
start_discussion(pid, SIZE_MAX);
2020-10-18 00:16:39 +00:00
return;
2020-10-17 22:51:22 +00:00
}
} else if (strcmp(input, "check tasks") == 0) {
2020-10-16 23:55:27 +00:00
player_list_tasks(pid);
return;
2020-10-20 18:39:45 +00:00
} else if (strcmp(input, "look at monitors") == 0) {
list_rooms_with_players(pid);
return;
} else if (strcmp(input, "help") == 0) {
snprintf(buf, sizeof(buf), "Commands: help, examine room, go [room], murder crewmate, report, check tasks");
write(players[pid].fd, buf, strlen(buf));
switch (players[pid].location) {
case LOC_CAFETERIA:
snprintf(buf, sizeof(buf), "\ncommands in this room: press emergency button\n# ");
break;
case LOC_SECURITY:
snprintf(buf, sizeof(buf), "\ncommands in this room: look at monitor\n# ");
break;
default:
snprintf(buf, sizeof(buf), "\n# ");
break;
}
2020-10-18 15:35:24 +00:00
} else if (strncmp(input, "map", 3) == 0) {
2020-11-07 16:54:13 +00:00
write(players[pid].fd, map, strlen(map));
2020-10-18 15:35:24 +00:00
snprintf(buf, sizeof(buf), "# ");
2020-10-17 10:24:15 +00:00
} else {
// check if it was a task
task_id = TASK_SHORT_COUNT + TASK_LONG_COUNT;
2020-10-17 10:24:15 +00:00
task_is_long = 0;
for (size_t i = 0; i < TASK_SHORT_COUNT; i++) {
2020-10-17 10:24:15 +00:00
if(strcmp(input, short_task_descriptions[i]) == 0) {
task_id = i;
break;
}
}
for (size_t i = 0; i < TASK_LONG_COUNT; i++) {
2020-10-19 13:43:10 +00:00
for(int k=0;k<2;k++) {
if(strcmp(input, long_task_descriptions[i][k]) == 0) {
// Check if player has the task
for(int l=0; l<NUM_LONG; l++) {
if (players[pid].long_tasks[l] == i &&
players[pid].long_tasks_done[l] == k) {
task_id = i;
task_is_long = 1;
}
}
}
2020-10-17 10:24:15 +00:00
}
}
if (task_id == TASK_SHORT_COUNT + TASK_LONG_COUNT) {
snprintf(buf, sizeof(buf), "Invalid instruction\n# ");
2020-10-17 10:24:15 +00:00
} else {
// check it was in the right room
if (strstr(input, locations[players[pid].location]) != NULL) {
task_completed(pid, task_id, task_is_long);
2020-10-18 15:37:35 +00:00
if (state.stage == STAGE_PLAYING) {
snprintf(buf, sizeof(buf), "Completed task\n# ");
} else {
buf[0] = '\0';
}
2020-10-17 10:24:15 +00:00
} else {
snprintf(buf, sizeof(buf), "You're in the wrong place for that\n# ");
2020-10-17 10:24:15 +00:00
}
}
2020-10-16 22:11:52 +00:00
}
write(players[pid].fd, buf, strlen(buf));
}
void
start_game()
{
2020-10-21 17:58:46 +00:00
int impostornum, assigned;
2020-10-16 22:11:52 +00:00
char buf[200];
unsigned temp;
2020-10-16 23:55:27 +00:00
2020-10-16 22:11:52 +00:00
broadcast("---------- [ Game is starting ] ----------", -1);
2020-10-20 18:35:59 +00:00
broadcast("\a", -1); /* Alarm beep for y'all multitaskers */
2020-10-19 17:41:48 +00:00
state.stage = STAGE_PLAYING;
2020-10-16 22:11:52 +00:00
state.players = 0;
for(int i=0; i<NUM_PLAYERS; i++) {
if(players[i].fd != -1) {
state.players++;
}
}
2020-10-21 17:58:46 +00:00
// Pick an impostor
impostornum = random_num(state.players);
2020-10-16 22:11:52 +00:00
assigned = 0;
for(int i=0; i<NUM_PLAYERS; i++) {
if(players[i].fd == -1)
continue;
players[i].stage = PLAYER_STAGE_MAIN;
2020-10-17 09:29:47 +00:00
players[i].location = LOC_CAFETERIA;
players[i].state = PLAYER_STATE_ALIVE;
2020-10-16 23:55:27 +00:00
// Assign NUM_SHORT random short tasks
for(int j=0;j<NUM_SHORT;j++) {
retry:
temp = (unsigned)random_num(TASK_SHORT_COUNT);
2020-10-16 23:55:27 +00:00
for(int k=0;k<NUM_SHORT;k++) {
if(players[i].short_tasks[k] == temp)
goto retry;
}
players[i].short_tasks[j] = temp;
players[i].short_tasks_done[j] = 0;
}
2020-10-17 09:52:35 +00:00
// Assign NUM_LONG random long tasks
for (size_t j = 0; j < NUM_LONG; j++) {
retry2:
temp = (unsigned)random_num(TASK_LONG_COUNT);
for (size_t k = 0; k < NUM_LONG; k++) {
2020-10-17 09:52:35 +00:00
if(players[i].long_tasks[k] == temp)
goto retry2;
}
players[i].long_tasks[j] = temp;
players[i].long_tasks_done[j] = 0;
}
2020-10-21 17:58:46 +00:00
if (assigned == impostornum) {
players[i].is_impostor = 1;
snprintf(buf, sizeof(buf), "You are the impostor, kill everyone without getting noticed.\n");
2020-10-16 22:11:52 +00:00
} else {
2020-10-21 17:58:46 +00:00
players[i].is_impostor = 0;
snprintf(buf, sizeof(buf), "You are a crewmate, complete your tasks before everyone is killed.\n");
2020-10-16 22:11:52 +00:00
}
write(players[i].fd, buf, strlen(buf));
assigned++;
}
2020-10-17 23:53:25 +00:00
// dramatic pause
for(int i=0;i<5;i++) {
sleep(1);
broadcast(".", -1);
}
for(int i=0;i<NUM_PLAYERS;i++) {
if(players[i].fd == -1)
continue;
2020-10-21 17:58:46 +00:00
if(players[i].is_impostor) {
2020-11-13 23:54:10 +00:00
snprintf(buf, sizeof(buf), "You are in a spaceship, the other %d crew members think you're one of them\n# ", assigned - 1);
2020-10-17 23:53:25 +00:00
write(players[i].fd, buf, strlen(buf));
} else {
snprintf(buf, sizeof(buf), "You are in a spaceship, one of the crew of %d people\n", assigned);
2020-10-17 23:53:25 +00:00
write(players[i].fd, buf, strlen(buf));
snprintf(buf, sizeof(buf), "The tasks have been handed out and the daily routine is starting up, but there are rumors one of your fellow crewmates isn't a crewmate at all.\n# ");
2020-10-17 23:53:25 +00:00
write(players[i].fd, buf, strlen(buf));
}
}
2020-10-16 22:11:52 +00:00
}
void reassign_admin() {
char buf[100];
for (int i= 0; i < NUM_PLAYERS; i++) {
if (players[i].fd == -1 || players[i].stage == PLAYER_STAGE_NAME)
continue;
players[i].is_admin = 1;
2020-10-20 19:05:08 +00:00
state.has_admin = 1;
snprintf(buf, sizeof(buf), " ** Admin left, new admin is %s **\n", players[i].name);
broadcast(buf, -1);
return;
}
}
void
set(char *buf, size_t buf_len, int fd, int pid)
{
if (strncmp(&buf[5],"kill-cooldown", 13) == 0) {
char *nextptr = NULL;
int value = strtol(&buf[19], &nextptr, 10);
if (nextptr == &buf[19]) {
const char *msg = "Error: you didn't enter any number. Leaving current value...\n";
write(fd, msg, strlen(msg));
} else if (value < 0) {
const char *msg = "Error: negative numbers aren't allowed. Leaving current value...\n";
write(fd, msg, strlen(msg));
} else if (nextptr != NULL && nextptr[0] == '\0') {
state.impostor_cooldown = value;
2020-10-21 17:58:46 +00:00
snprintf(buf, buf_len, "%s changed impostor cooldown to %d.", players[pid].name, state.impostor_cooldown);
broadcast(buf, -1);
} else {
const char *msg = "Error: invalid input. Leaving current value...\n";
write(fd, msg, strlen(msg));
}
} else {
const char *msg = "Error: you didn't write a valid property.\n";
write(fd, msg, strlen(msg));
}
}
2020-10-21 17:57:14 +00:00
void
list_set(int pid)
{
char buf[100];
snprintf(buf, sizeof(buf), " kill-cooldown = %d\n", state.impostor_cooldown);
write(players[pid].fd, buf, strlen(buf));
}
2020-10-16 22:11:52 +00:00
int
handle_input(int fd)
{
char buf[200] = {0};
2020-10-16 22:11:52 +00:00
char buf2[300];
ssize_t len;
size_t pid;
2020-10-16 22:11:52 +00:00
// Find player for fd
for (pid = 0; pid < NUM_PLAYERS; pid++) {
if (players[pid].fd == fd) {
break;
}
}
// Get the input
len = read(fd, buf, 199);
2020-10-16 22:11:52 +00:00
if (len < 0) {
printf("Read error from player %zu\n", pid);
2020-10-17 14:19:22 +00:00
players[pid].fd = -1;
if (players[pid].stage != PLAYER_STAGE_NAME) {
snprintf(buf, sizeof(buf), "Player [%s] disconnected.", players[pid].name);
2021-01-17 20:41:35 +00:00
players[pid].name[0] = '\0';
printf("Sending disconnection message\n");
broadcast(buf, -1);
}
if (players[pid].is_admin) {
2020-10-20 17:24:10 +00:00
state.has_admin = 0;
reassign_admin();
}
2020-10-16 22:11:52 +00:00
return -1;
}
if (len == 0) {
printf("Received EOF from player %zu\n", pid);
2020-10-17 14:19:22 +00:00
players[pid].fd = -1;
2020-10-17 23:25:27 +00:00
if (players[pid].stage != PLAYER_STAGE_NAME) {
snprintf(buf, sizeof(buf), "Player [%s] left the game.", players[pid].name);
2021-01-17 20:41:35 +00:00
players[pid].name[0] = '\0';
2020-10-17 23:25:27 +00:00
printf("Sending parting message\n");
broadcast(buf, -1);
}
if (players[pid].is_admin) {
2020-10-20 17:24:10 +00:00
state.has_admin = 0;
reassign_admin();
}
2020-10-16 22:11:52 +00:00
return -2;
}
for (size_t i = 0; i < sizeof(buf); i++) {
2020-10-17 10:49:03 +00:00
if (buf[i] == '\n' || buf[i] == '\r') {
2020-10-16 22:11:52 +00:00
buf[i] = '\0';
break;
}
}
2020-10-19 17:41:48 +00:00
printf("%zu: %s\n", pid, buf);
2020-10-16 22:11:52 +00:00
switch(players[pid].stage) {
case PLAYER_STAGE_NAME:
// Setting the name after connection and informing the lobby
if(strlen(buf) < MIN_NAME) {
snprintf(buf, sizeof(buf), "Too short, pick another name\n> ");
2020-10-17 14:19:22 +00:00
write(fd, buf, strlen(buf));
return 0;
}
if(strlen(buf) > MAX_NAME) {
snprintf(buf, sizeof(buf), "Too long, pick another name\n> ");
2020-10-17 14:19:22 +00:00
write(fd, buf, strlen(buf));
return 0;
}
2021-01-17 20:41:35 +00:00
for (size_t i = 0; i < NUM_PLAYERS; i++) {
if (strcmp(players[i].name, buf) == 0) {
snprintf(buf, sizeof(buf), "Taken, pick another name\n> ");
write(fd, buf, strlen(buf));
return 0;
}
}
for (size_t i = 0; i < strlen(buf); i++) {
if(!isprint(buf[i])) {
snprintf(buf, sizeof(buf), "Invalid char, pick another name\n> ");
2020-10-17 14:19:22 +00:00
write(fd, buf, strlen(buf));
return 0;
}
}
2020-10-16 22:11:52 +00:00
strcpy(players[pid].name, buf);
2020-10-17 14:19:22 +00:00
if (state.stage == STAGE_LOBBY) {
snprintf(buf, sizeof(buf), "[%s] has joined the lobby", players[pid].name);
broadcast(buf, fd);
players[pid].stage = PLAYER_STAGE_LOBBY;
} else
players[pid].stage = PLAYER_STAGE_WAITING;
break;
case PLAYER_STAGE_WAITING:
2020-10-16 22:11:52 +00:00
break;
case PLAYER_STAGE_LOBBY:
// Chat message in the lobby
if (buf[0] == '/') {
if (strcmp(buf, "/start") == 0) {
2020-10-16 22:11:52 +00:00
if(players[pid].is_admin) {
start_game();
} else {
const char *msg = "You don't have permission to /start\n";
write(fd, msg, strlen(msg));
2020-10-16 22:11:52 +00:00
}
} else if (strcmp(buf, "/shrug") == 0) {
snprintf(buf2, sizeof(buf2), "[%s] ¯\\_(ツ)_/¯", players[pid].name);
2020-10-17 22:20:56 +00:00
broadcast(buf2, fd);
} else if (strncmp(buf, "/me ", 4) == 0) {
snprintf(buf2, sizeof(buf2), " * [%s] %s", players[pid].name, &buf[4]);
2020-10-17 22:28:31 +00:00
broadcast(buf2, fd);
} else if (strcmp(buf, "/help") == 0) {
snprintf(buf, sizeof(buf), "Commands: /start, /list and more\n");
2020-10-17 22:44:19 +00:00
write(fd, buf, strlen(buf));
} else if (strcmp(buf, "/list") == 0) {
2020-10-17 22:44:19 +00:00
for(int i=0;i<NUM_PLAYERS;i++){
if (players[i].fd == -1)
continue;
if (players[i].stage == PLAYER_STAGE_NAME) {
snprintf(buf, sizeof(buf), " %d: -[ setting name ]-\n", i);
2020-10-17 22:44:19 +00:00
} else if (players[i].is_admin) {
snprintf(buf, sizeof(buf), " %d: %s (admin)\n", i, players[i].name);
2020-10-17 22:44:19 +00:00
} else {
snprintf(buf, sizeof(buf), " %d: %s\n", i, players[i].name);
2020-10-17 22:44:19 +00:00
}
write(fd, buf, strlen(buf));
}
2020-10-21 17:57:14 +00:00
} else if (strncmp(buf, "/set", 4) == 0) {
if (strlen(buf) == 4) {
list_set(pid);
return 0;
}
if (players[pid].is_admin)
set(buf, sizeof(buf), fd, pid);
else {
const char *msg = "You don't have permission to /set\n";
write(fd, msg, strlen(msg));
}
2020-10-16 22:11:52 +00:00
}
} else {
int hasvalidchar = 0;
for (size_t i = 0; i < strlen(buf); i++) {
if (!isprint(buf[i]))
2020-10-17 22:16:20 +00:00
buf[i] = '\0';
else if (!isspace(buf[i]))
hasvalidchar = 1;
}
if (hasvalidchar) {
snprintf(buf2, sizeof(buf2), "[%s]: %s", players[pid].name, buf);
broadcast(buf2, fd);
2020-10-17 22:16:20 +00:00
}
2020-10-16 22:11:52 +00:00
}
break;
case PLAYER_STAGE_MAIN:
// Main game adventure loop
adventure(pid, buf);
break;
case PLAYER_STAGE_DISCUSS:
// Main discussion loop
discussion(pid, buf);
break;
}
return 0;
}
2020-10-17 11:37:10 +00:00
int
welcome_player(int fd)
2020-10-16 22:11:52 +00:00
{
char buf[100];
for (size_t i = 0; i < sizeof(players); i++) {
2020-10-16 22:11:52 +00:00
if (players[i].fd > 0) {
continue;
}
snprintf(buf, sizeof(buf), "among-sus server: version %s\n", VERSION);
write(fd, buf, strlen(buf));
if(state.stage != STAGE_LOBBY) {
snprintf(buf, sizeof(buf), "There is a game in progress, waiting for the match to finish...\n");
write(fd, buf, strlen(buf));
}
players[i].fd = fd;
2020-10-20 17:24:10 +00:00
players[i].is_admin = 0;
if (!state.has_admin) {
state.has_admin = 1;
players[i].is_admin = 1;
}
players[i].stage = PLAYER_STAGE_NAME;
snprintf(buf, sizeof(buf), "Welcome player %zu!\n\nEnter your name:\n> ", i);
write(fd, buf, strlen(buf));
printf("Assigned player to spot %zu\n", i);
return 0;
2020-10-16 22:11:52 +00:00
}
snprintf(buf, sizeof(buf), "There are no spots available, goodbye!\n");
2020-10-16 22:11:52 +00:00
write(fd, buf, strlen(buf));
close(fd);
2020-10-17 11:37:10 +00:00
return -1;
2020-10-16 22:11:52 +00:00
}
int
2020-11-07 22:21:04 +00:00
main(int argc, char *argv[])
2020-10-16 22:11:52 +00:00
{
// Set default settings
state.impostor_cooldown = 1;
uint16_t port = 1234;
2020-11-07 22:21:04 +00:00
char *endptr = NULL;
int opt;
while ((opt = getopt(argc, argv, "hp:")) != -1) {
switch (opt) {
2020-12-06 17:55:30 +00:00
case 'p':;
errno = 0;
long _port = strtol(optarg, &endptr, 10);
if (*endptr != '\0' || errno != 0 ||
_port <= 0 || _port >= 65536) {
fprintf(stderr, "Invalid port: %s\n", optarg);
exit(EXIT_FAILURE);
}
port = (uint16_t) _port;
2020-11-07 22:21:04 +00:00
break;
case 'h':
printf("%s", usage);
exit(EXIT_SUCCESS);
2020-12-06 13:36:13 +00:00
default:
2020-11-07 22:21:04 +00:00
printf("%s", usage);
exit(EXIT_FAILURE);
}
}
int listen_fd, listen6_fd, new_fd, i;
2020-10-17 21:41:26 +00:00
socklen_t client_size;
2020-11-09 21:43:47 +00:00
struct sockaddr_in listen_addr = {0}, client_addr = {0};
struct sockaddr_in6 listen6_addr = {0};
2020-10-16 22:11:52 +00:00
for (i = 0; i < NUM_PLAYERS; i++) {
players[i].fd = -1;
}
srand((unsigned)time(NULL));
2020-10-16 22:11:52 +00:00
2020-12-09 17:39:59 +00:00
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("IPv4 socket");
exit(EXIT_FAILURE);
}
if ((listen6_fd = socket(AF_INET6, SOCK_STREAM, 0)) == -1) {
perror("IPv6 socket");
exit(EXIT_FAILURE);
}
2020-10-16 22:11:52 +00:00
2020-10-19 02:56:22 +00:00
i = 1;
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,
&i, sizeof(i))) {
2020-12-09 17:39:59 +00:00
perror("IPv4 setsockopt");
2020-10-19 02:56:22 +00:00
exit(EXIT_FAILURE);
}
2020-10-29 20:28:37 +00:00
if (setsockopt(listen6_fd, SOL_SOCKET, SO_REUSEADDR,
&i, sizeof(i))) {
2020-12-09 17:39:59 +00:00
perror("IPv6 setsockopt");
2020-10-29 20:28:37 +00:00
exit(EXIT_FAILURE);
}
2020-10-16 22:11:52 +00:00
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);
listen_addr.sin_port = htons(port);
2020-10-19 03:40:31 +00:00
listen6_addr.sin6_family = AF_INET6;
listen6_addr.sin6_addr = in6addr_any;
listen6_addr.sin6_port = htons(port);
if (setsockopt(listen6_fd, IPPROTO_IPV6, IPV6_V6ONLY,
&i, sizeof(i))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
if (setsockopt(listen6_fd, IPPROTO_IPV6, IPV6_V6ONLY,
&i, sizeof(i))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
2020-10-19 03:40:31 +00:00
2020-10-16 22:11:52 +00:00
if (bind(listen_fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) < 0) {
2020-10-19 03:40:31 +00:00
perror("ipv4 bind");
return -1;
}
if (bind(listen6_fd, (struct sockaddr *)&listen6_addr, sizeof(listen6_addr)) < 0) {
perror("ipv6 bind");
2020-10-16 22:11:52 +00:00
return -1;
}
listen(listen_fd, 5);
2020-10-19 03:40:31 +00:00
listen(listen6_fd, 5);
2020-10-16 22:11:52 +00:00
printf("Listening on :%d\n", port);
state.stage = STAGE_LOBBY;
FD_ZERO(&afds);
FD_SET(listen_fd, &afds);
2020-10-19 03:40:31 +00:00
FD_SET(listen6_fd, &afds);
2020-10-16 22:11:52 +00:00
while (1) {
rfds = afds;
if (select(FD_SETSIZE, &rfds, NULL, NULL, NULL) < 0) {
perror("select");
2020-10-16 22:11:52 +00:00
exit(EXIT_FAILURE);
}
for (i = 0; i < FD_SETSIZE; ++i) {
if (FD_ISSET (i, &rfds)) {
2020-10-19 03:40:31 +00:00
if (i == listen_fd || i == listen6_fd) {
2020-10-16 22:11:52 +00:00
printf("welcome client!\n");
client_size = sizeof(client_addr);
2020-10-19 03:40:31 +00:00
new_fd = accept(i, (struct sockaddr *)&client_addr, &client_size);
2020-10-16 22:11:52 +00:00
if (new_fd < 0) {
perror("accept");
2020-10-16 22:11:52 +00:00
exit(EXIT_FAILURE);
}
printf("New connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
FD_SET(new_fd, &afds);
if(welcome_player(new_fd)<0){
2020-10-17 11:37:10 +00:00
FD_CLR(new_fd, &afds);
}
2020-10-16 22:11:52 +00:00
} else {
if(handle_input(i) < 0) {
close(i);
FD_CLR(i, &afds);
}
}
}
}
}
}