Skip to content

Commit

Permalink
Added 2 player mode
Browse files Browse the repository at this point in the history
  • Loading branch information
esoteric-programmer committed Jan 15, 2017
1 parent 930b135 commit 5af8683
Show file tree
Hide file tree
Showing 35 changed files with 3,810 additions and 538 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
Version 0.6 alpha
- Added 2 player mode via 3DS local wifi connection
- In-game background color can be switched to dark blue (AMIGA style)

Version 0.5.1
- Fixed lemming start position

Expand Down
15 changes: 8 additions & 7 deletions CONTROLS.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Main menu:

A, START start 1 player game
B, X, SELECT select level
B, X, SELECT start 2 player game
Y enter settings menu
UP, DOWN change rating; switch between original Lemmings and "Oh no! More Lemmings"
touch touch at a lemming to trigger one of the actions above
Expand Down Expand Up @@ -32,14 +32,15 @@ In game:

C-PAD move cursor
A click at cursor position
B pause/resume
B pause/resume (not in 2 player game)
X select next skill
Y select previous skill
D UP increase release rate
D DOWN decrease release rate
D RIGHT step one frame while game is paused
L time runs 3 times faster while held down
D UP increase release rate (not in 2 player game)
D DOWN decrease release rate (not in 2 player game)
D RIGHT step one frame while game is paused (not in 2 player game)
L time runs 3 times faster while held down (not in 2 player game)
R + C-PAD scroll horizontal
SELECT highlight/select non-priorized lemming
START press twice to nuke level (give up); does not work while game is paused
touch move cursor to touch position and click
in 2 player game, both players must nuke the level at the same time
touch move cursor to touch position and click there
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ BUILD_FLAGS :=
RUN_FLAGS :=

VERSION_MAJOR := 0
VERSION_MINOR := 5
VERSION_MICRO := 1
VERSION_MINOR := 6
VERSION_MICRO := 0

# 3DS CONFIGURATION #

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Copy the required* files into the following subfolders of /lemmings folder:
orig\_demo, orig, ohno\_demo, ohno, xmas91, xmas92, holi93\_demo, holi93, holi94\_demo and/or holi94. You need to fill at least one folder to play the game.
To avoid duplicates, you may only fill these folders if you own all Lemmings games:
orig, ohno, xmas91, xmas92, holi94
For multiplayer mode, fill the subfolders of /lemmings/2p of the host system
(at the moment, you have to fill the folder called orig).

The game uses the NDSP service to play audio. Thus you need a DSP firm dump to have audio.
You may want to use your own sound and/or music files.
Expand Down
2 changes: 1 addition & 1 deletion demos/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ However, they are protected by copyright law, so they must not be modified,
and the graphics, sound, and levels must not be included in other software.

Therefore, the games are in this separate folder and not in the
xmas91 and xmas92 folders of the main project.
lemmings folder of the main project.

You can play the demos using DOSBox.
8 changes: 8 additions & 0 deletions include/2p_button.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef BUTTON2P_H
#define BUTTON2P_H
#include <3ds.h>
// main menu button 2 player that replaces "new game" button (orig and ohno)
// 93 x 27
extern const u8 main_menu_button_2p[];
extern const u8 main_menu_xmas_button_2p[];
#endif
45 changes: 45 additions & 0 deletions include/control.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef CONTROL_H
#define CONTROL_H
#include <3ds.h>
#include "level.h"

#define ACTION_MOVE_CURSOR_RIGHT (1<< 0)
#define ACTION_MOVE_CURSOR_LEFT (1<< 1)
Expand Down Expand Up @@ -33,5 +34,49 @@
#define ACTION_QUIT_GAME (1<<28)
#define ACTION_STEP_FRAME (1<<29)

#define BOTTOM_SCREEN_Y_OFFSET 32

#define MAX_ACTION_QUEUE_SIZE 3
struct ActionQueue {
enum Action {
ACTIONQUEUE_NOP, // no action
ACTIONQUEUE_NUKE, // 0 = nuke; 1 = exit immediately
ACTIONQUEUE_ASSIGN_SKILL, // to Lemming with id stored in param (lem1) or param2 (lem2); skill stored in param3
ACTIONQUEUE_TOGGLE_PAUSE,
ACTIONQUEUE_FRAME_FORWARD, // (s8)param: number of frames (handling of negative values not implemented yet)
ACTIONQUEUE_CHANGE_RATE // (s8)param = changing
} action;
u8 param;
u8 param2;
u8 param3;
};

struct InputState {
// number of (input-)frames since user pressed a button
// to change the release rate of lemmings
u8 change_rate_hold;
// time elapsed since user pressed the nuke button the last time.
// 0 if this is long ago.
u8 time_since_nuke_pressed;
u8 speed_up;
u8 nonprio_lem;
u8 skill;
struct {s16 x; s16 y;} cursor;
u16 x_pos;
u8 num_actions; // number of actions in queue
struct ActionQueue action_queue[MAX_ACTION_QUEUE_SIZE];
};

u64 get_action(u32 kDown, u32 kHeld, circlePosition cpad, circlePosition* params);
void init_io_state(struct InputState* io_state, u16 x_pos);
int add_action(struct InputState* io_state, enum Action action, u8 param, u8 param2, u8 param3);
int read_io(struct Level* level, struct InputState* io_state, u8 player);
int process_action_queue(
// actions to perform (invalid actions will be replaced by ACTIONQUEUE_NOP)
struct ActionQueue* action_queue,
u8 num_actions,
struct Level* level,
u8 player_id,
// if multiplayer is set, some actions will be disabled
u8 multiplayer);
#endif
6 changes: 5 additions & 1 deletion include/draw.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "main_data.h"
#include "level.h"
#include "lemming_data.h"
#include "control.h"

typedef enum
{
Expand Down Expand Up @@ -50,6 +51,7 @@ int draw_level(
s16 y,
u16 w,
u16 h,
s16 x_offset,
struct Level* level,
struct MainInGameData* main_data,
u32* palette);
Expand Down Expand Up @@ -78,8 +80,10 @@ void draw_menu_text(
int draw_toolbar(
struct MainInGameData* data,
struct Level* level,
struct InputState* io_state,
const char* text,
u32* highperf_palette);
u32* highperf_palette,
u8 player);

// draw lemmings of all players (only 2 players supported)
void draw_lemmings(
Expand Down
14 changes: 14 additions & 0 deletions include/gamespecific_2p.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef GAMESPECIFIC_2P_H
#define GAMESPECIFIC_2P_H
#include <3ds.h>
#include "gamespecific.h"

struct GameSpecific2P {
const char* const level_path;
const char* const ressource_path;
const u32* const ingame_palette;
const u8 size_swap_exit;
const u8* const swap_exit;
};
extern const struct GameSpecific2P import_2p[2];
#endif
4 changes: 3 additions & 1 deletion include/import_level.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ int read_level(
struct Data** vgagr_s0,
struct Data** vgagr_s1,
struct Data** vgaspec);
// count number of available custom files in specific folder
u8 count_custom_levels(const char* path);
// import custom level (from uncompressed lvl file)
int read_level_file(
const char* path,
const char* filename,
const char* ressource_path,
void* level,
void* ground_data,
struct Data** vgagr_s0,
Expand Down
14 changes: 14 additions & 0 deletions include/ingame.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define INGAME_H
#include <3ds.h>
#include "level.h"
#include "control.h"
#include "main_data.h"
#include "draw.h"

Expand All @@ -18,6 +19,19 @@ struct LevelResult {
u8 exit_reason;
};

int level_step(
struct MainInGameData* main_data,
struct Level* level,
// *lemming_inout = 1, iff a any lemming enters or exits the level (without dying)
u8* lemming_inout);

void render_level_frame(
const char* level_id, // e.g. FUN 14
struct MainInGameData* main_data,
struct Level* level,
struct InputState* io_state,
u8 player);

// returns: percentage saved
struct LevelResult run_level(
struct Level* level,
Expand Down
2 changes: 1 addition & 1 deletion include/lemming.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
extern const int action_image_offsets[18][2];

void init_lemmings(struct Level* level);
void add_lemming(struct Level* level);
int add_lemming(struct Level* level);
u8 lemmings_left(struct LevelPlayer* player_state); // get number of lemmings that did not have entered the level yet.
void nuke(struct LevelPlayer* player);

Expand Down
13 changes: 2 additions & 11 deletions include/lemming_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,6 @@
#define LEM_MAX_Y 163 // LEMMING_MAX_Y
#define LEM_MAX_FALLING 60 // MAX_FALLDISTANCECOUNT

#define OBJECT_EXIT 1
#define OBJECT_FORCE_LEFT 2
#define OBJECT_FORCE_RIGHT 3
#define OBJECT_TRAP 4
#define OBJECT_WATER 5
#define OBJECT_FIRE 6
#define OBJECT_ONEWAY_LEFT 7
#define OBJECT_ONEWAY_RIGHT 8
#define OBJECT_STEEL 9
#define OBJECT_BLOCKER 10

struct Lemming {
u8 removed;
u8 current_action;
Expand All @@ -59,5 +48,7 @@ struct Lemming {
u8 object_below;
u8 object_in_front;
u8 saved_object_map[9];
u8 exit_counts_for; // player id the exiting lemming counts for
u8 player; // owner of this lemming
};
#endif
15 changes: 13 additions & 2 deletions include/level.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
#include "settings.h"
#include "lemming_data.h"

// object effects
#define OBJECT_EXIT 1
#define OBJECT_FORCE_LEFT 2
#define OBJECT_FORCE_RIGHT 3
#define OBJECT_TRAP 4
#define OBJECT_WATER 5
#define OBJECT_FIRE 6
#define OBJECT_ONEWAY_LEFT 7
#define OBJECT_ONEWAY_RIGHT 8
#define OBJECT_STEEL 9
#define OBJECT_BLOCKER 10

struct ObjectType {
u16 flags;
u16 width;
Expand Down Expand Up @@ -35,15 +47,14 @@ struct ObjectInstance {
struct LevelPlayer {
u8 max_lemmings;
u8 skills[8];
u8 selected_skill;
u16 x_pos;
struct {s16 x; s16 y;} cursor;
u8 nuking;
u8 timer_assign;
u8 next_lemming_id;
// player one's lemmings and player two's lemmings that used the exit
// of that player this LevelPlayer struct corresponds to.
u8 rescued[2];
u16 request_common_nuke; // multiplayer only. set when player wants to nuke. count down each frame, so the other player must request nuking in the same time slot
struct Lemming lemmings[MAX_NUM_OF_LEMMINGS];
};

Expand Down
4 changes: 4 additions & 0 deletions include/menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#define MENU_ACTION_LEVEL_SELECTED 5
#define RESULT_ACTION_NEXT 6
#define RESULT_ACTION_CANCEL 7
#define MENU_ACTION_START_MULTI_PLAYER 8
#define MENU_HOST_GAME 9
#define MENU_HOST_REJECT_CLIENT 10
#define MENU_CLIENT_QUIT 11
#define MENU_EXIT_GAME 127

int main_menu(
Expand Down
97 changes: 97 additions & 0 deletions include/network.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#ifndef NETWORK_H
#define NETWORK_H
#include <3ds.h>
#include "level.h"
#include "main_data.h"
#include "control.h"

// network messages
#define NW_ERROR 0 // communication error (maybe just cancel connection...; implement handling later)
#define NW_INITIALIZE 1 // confirm that the server WILL start the game; params: num of (active) players, num of lemmings per player, player id of receiver
#define NW_LEVEL_INFO 2 // receive level data (first packet: Info. then: Chunks)
#define NW_LEVEL_DATA_CHUNK 3 // send back (with zero length) to confirm receivement
#define NW_LEVEL_SENT 4 // entire level has been sent
#define NW_RUN_FRAME 5 // step a given number of frames without action (info from server)
#define NW_USER_INPUT 6 // screen positions and mouse positions of all players (to inform other); own position should be sent by clients with this message, too
#define NW_LVLRESULT 7 // indicate end of level and send results (lemmings saved by each player)
#define NW_READY_TO_PROCEED 8 // client informs server that he is ready to start the next level (user has read the result message)

struct NW_GameInit {
u8 msg_type;
u8 num_players;
u8 lemmings_per_player[2];
u8 receiver_id;
};

struct NW_LevelData_Info {
u8 msg_type;
u8 swap_exits;
u32 vgagr_s0_size;
u32 vgagr_s1_size;
u32 vgaspec_size;
u32 ingame_basis_palette[7];
u8 ground_data[1056];
};

struct NW_LevelData_Chunk {
u8 msg_type;
u8 type; // 0: level; 1: vgagr_s0; 2: vgagr_s1; 3: vgaspec
u32 offset;
u16 length;
u8 data[0];
};

struct NW_User_Input {
u8 msg_type;
u8 player_id;
u16 x_pos;
u32 frame_id; // frame id the user made the input (only set when sent by server)
u32 input_id; // id of this operation; starting with 1; no id must be missed
u8 num_actions;
struct ActionQueue action_queue[MAX_ACTION_QUEUE_SIZE];
};

struct NW_Run_Frame {
u8 msg_type;
u32 frame_id; // current frame id
u32 required_input_id; // this frame must only be rendered if the required_input_id has been received already
};

struct NW_Level_Result {
u8 msg_type;
u8 lemmings_saved[2];
u16 won[2];
};

int server_prepare_level(
udsBindContext* bindctx,
const u8* lemmings, // number of lemmings the players start with
u8 game_id,
u8 level_id,
struct Level* output);
int client_prepare_level(
udsBindContext* bindctx,
const u8* lemmings, // number of lemmings the players start with
struct Level* output);

int server_run_level(
udsBindContext* bindctx,
struct Level* level,
const char* level_id,
u8* lemmings, // number of lemmings the players have rescued
struct MainMenuData* menu_data,
struct MainInGameData* main_data);
int client_run_level(
udsBindContext* bindctx,
struct Level* level,
const char* level_id,
u8* lemmings,
u16* won,
struct MainMenuData* menu_data,
struct MainInGameData* main_data);

int server_send_result(
udsBindContext* bindctx,
u8 lemmings[2],
u16 won[2]);
#endif
Loading

0 comments on commit 5af8683

Please sign in to comment.