Example sign with conversation.

This commit is contained in:
2024-10-20 15:31:04 -07:00
parent a74f285cb2
commit b2a3ca4411
12 changed files with 143 additions and 67 deletions

View File

@ -21,7 +21,11 @@
"maps": { "maps": {
"testmap": { "testmap": {
"bob": "Hello, I am Bob.", "bob": "Hello, I am Bob.",
"sign": "This is a sign." "sign": "This is a sign.",
"sign2": {
"1": "This is another sign.",
"2": "It has two lines."
}
} }
} }
} }

View File

@ -42,6 +42,15 @@
"y": 8, "y": 8,
"text": "maps.testmap.sign" "text": "maps.testmap.sign"
}, },
{
"type": 3,
"x": 2,
"y": 2,
"texts": [
"maps.testmap.sign2.1",
"maps.testmap.sign2.2"
]
},
{ {
"type": 4, "type": 4,
"x": 6, "x": 6,

View File

@ -48,12 +48,32 @@ void assetMapLoadEntity(
case ENTITY_TYPE_SIGN: case ENTITY_TYPE_SIGN:
val = assetJsonGetObjectValue(jEnt, "text"); val = assetJsonGetObjectValue(jEnt, "text");
if(val != NULL) { if(val != NULL) {
assertTrue( assertTrue(
val->type == ASSET_JSON_DATA_TYPE_STRING, val->type == ASSET_JSON_DATA_TYPE_STRING,
"assetMapLoad: Sign text is not a string!" "assetMapLoad: Sign text is not a string!"
); );
signTextSet(&ent->sign, val->string); signTextAppend(&ent->sign, val->string);
}
val = assetJsonGetObjectValue(jEnt, "texts");
if(val != NULL) {
assertTrue(
val->type == ASSET_JSON_DATA_TYPE_ARRAY,
"assetMapLoad: Sign texts is not an array!"
);
assertTrue(
val->array.length <= SIGN_TEXT_COUNT_MAX,
"assetMapLoad: Too many sign texts!"
);
for(int32_t i = 0; i < val->array.length; i++) {
assetjson_t *subVal = val->array.value[i];
assertTrue(
subVal->type == ASSET_JSON_DATA_TYPE_STRING,
"assetMapLoad: Sign text is not a string!"
);
signTextAppend(&ent->sign, subVal->string);
}
} }
break; break;

View File

@ -46,7 +46,6 @@ void gameInit() {
gameupdateresult_t gameUpdate(const float_t delta) { gameupdateresult_t gameUpdate(const float_t delta) {
timeUpdate(delta); timeUpdate(delta);
inputUpdate(); inputUpdate();
conversationUpdate();
switch(GAME.state) { switch(GAME.state) {
case GAME_STATE_INITIAL: case GAME_STATE_INITIAL:

View File

@ -10,10 +10,13 @@
#include "input.h" #include "input.h"
#include "ui/textbox.h" #include "ui/textbox.h"
#include "ui/testmenu.h" #include "ui/testmenu.h"
#include "rpg/conversation/conversation.h"
void gameStateOverworldUpdate() { void gameStateOverworldUpdate() {
textboxUpdate(); textboxUpdate();
testMenuUpdate(); testMenuUpdate();
conversationUpdate();
if(GAME.currentMap) mapUpdate(GAME.currentMap); if(GAME.currentMap) mapUpdate(GAME.currentMap);
if(inputWasPressed(INPUT_BIND_PAUSE)) GAME.state = GAME_STATE_PAUSED; if(inputWasPressed(INPUT_BIND_PAUSE)) GAME.state = GAME_STATE_PAUSED;
} }

View File

@ -30,49 +30,56 @@ void conversationUpdate() {
if(CONVERSATION.init == NULL || CONVERSATION.update == NULL) return; if(CONVERSATION.init == NULL || CONVERSATION.update == NULL) return;
uint16_t ret; uint16_t ret;
bool_t recheck = true;
// Init or update. // Init or update.
switch(CONVERSATION.index) { while(recheck) {
case CONVERSATION_NOT_INITIALIZED: switch(CONVERSATION.index) {
CONVERSATION.index = 0; case CONVERSATION_NOT_INITIALIZED:
ret = CONVERSATION.init( CONVERSATION.index = 0;
&CONVERSATION, CONVERSATION.index ret = CONVERSATION.init(
); &CONVERSATION, CONVERSATION.index
break; );
break;
default:
ret = CONVERSATION.update(&CONVERSATION, CONVERSATION.index); default:
break; ret = CONVERSATION.update(&CONVERSATION, CONVERSATION.index);
} break;
}
// Check ret value and update conversation.
switch(ret) {
case CONVERSATION_NEXT:
CONVERSATION.index++;
CONVERSATION.index = CONVERSATION.init(
&CONVERSATION, CONVERSATION.index
);
break;
case CONVERSATION_CONTINUE:
return;
case CONVERSATION_RESTART:
CONVERSATION.index = 0;
CONVERSATION.init(&CONVERSATION, CONVERSATION.index);
break;
case CONVERSATION_INVALID:
assertUnreachable("Invalid converstaion retval");
break;
case CONVERSATION_DONE: // Check ret value and update conversation.
CONVERSATION.init = NULL; switch(ret) {
CONVERSATION.update = NULL; case CONVERSATION_NEXT:
break; CONVERSATION.index++;
CONVERSATION.index = CONVERSATION.init(
default: &CONVERSATION, CONVERSATION.index
CONVERSATION.index = ret; );
break; break;
case CONVERSATION_CONTINUE:
recheck = false;
break;
case CONVERSATION_RESTART:
CONVERSATION.index = 0;
CONVERSATION.init(&CONVERSATION, CONVERSATION.index);
break;
case CONVERSATION_INVALID:
assertUnreachable("Invalid converstaion retval");
recheck = false;
break;
case CONVERSATION_DONE:
CONVERSATION.init = NULL;
CONVERSATION.update = NULL;
recheck = false;
break;
default:
CONVERSATION.index = ret;
break;
}
} }
} }

View File

@ -21,12 +21,12 @@ uint16_t conversationInteractEntityInit(
entity_t *e = (entity_t*)convo->data.entityInteract.entity; entity_t *e = (entity_t*)convo->data.entityInteract.entity;
switch(e->type) { switch(e->type) {
case ENTITY_TYPE_SIGN: case ENTITY_TYPE_SIGN:
textboxSetText("entities.sign.name", e->sign.text); textboxSetText("entities.sign.name", e->sign.texts[i]);
return 0; return i;
case ENTITY_TYPE_NPC: case ENTITY_TYPE_NPC:
textboxSetText(e->npc.name, e->npc.text); textboxSetText(e->npc.text.name, e->npc.text.text);
return 0; return i;
default: default:
assertUnreachable("Invalid entity type for conversation."); assertUnreachable("Invalid entity type for conversation.");
@ -38,11 +38,24 @@ uint16_t conversationInteractEntityUpdate(
conversation_t *convo, conversation_t *convo,
const uint16_t i const uint16_t i
) { ) {
switch(i) { assertNotNull(convo, "Conversation is NULL!");
case 0: assertNotNull(convo->data.entityInteract.entity, "Conversation user is NULL!");
return textboxIsOpen() ? CONVERSATION_CONTINUE : CONVERSATION_DONE;
entity_t *e = (entity_t*)convo->data.entityInteract.entity;
switch(e->type) {
case ENTITY_TYPE_SIGN:
if(textboxIsOpen()) return CONVERSATION_CONTINUE;
if(i == SIGN_TEXT_COUNT_MAX-1) return CONVERSATION_DONE;
if(e->sign.texts[i+1][0] == '\0') return CONVERSATION_DONE;
return CONVERSATION_NEXT;
default: default:
return CONVERSATION_INVALID; switch(i) {
case 0:
return textboxIsOpen() ? CONVERSATION_CONTINUE : CONVERSATION_DONE;
default:
return CONVERSATION_INVALID;
}
} }
} }

View File

@ -33,6 +33,7 @@ void entityInteractEntity(
conversationInteractEntityUpdate, conversationInteractEntityUpdate,
(conversationdata_t){ .entityInteract = { .entity = target } } (conversationdata_t){ .entityInteract = { .entity = target } }
); );
source->state = ENTITY_STATE_TALKING;
return; return;
case ENTITY_TYPE_SIGN: case ENTITY_TYPE_SIGN:
@ -41,6 +42,7 @@ void entityInteractEntity(
conversationInteractEntityUpdate, conversationInteractEntityUpdate,
(conversationdata_t){ .entityInteract = { .entity = target } } (conversationdata_t){ .entityInteract = { .entity = target } }
); );
source->state = ENTITY_STATE_TALKING;
return; return;
case ENTITY_TYPE_DOOR: case ENTITY_TYPE_DOOR:

View File

@ -10,18 +10,16 @@
void npcInit(npc_t *npc) { void npcInit(npc_t *npc) {
assertNotNull(npc, "npcInit: NPC is NULL!"); assertNotNull(npc, "npcInit: NPC is NULL!");
npc->name[0] = '\0';
npc->text[0] = '\0';
} }
void npcNameSet(npc_t *npc, const char_t *name) { void npcNameSet(npc_t *npc, const char_t *name) {
assertNotNull(npc, "npcNameSet: NPC is NULL!"); assertNotNull(npc, "npcNameSet: NPC is NULL!");
assertNotNull(name, "npcNameSet: Name is NULL!"); assertNotNull(name, "npcNameSet: Name is NULL!");
strncpy(npc->name, name, LANGUAGE_STRING_KEY_LENGTH_MAX); strncpy(npc->text.name, name, LANGUAGE_STRING_KEY_LENGTH_MAX);
} }
void npcTextSet(npc_t *npc, const char_t *text) { void npcTextSet(npc_t *npc, const char_t *text) {
assertNotNull(npc, "npcTextSet: NPC is NULL!"); assertNotNull(npc, "npcTextSet: NPC is NULL!");
assertNotNull(text, "npcTextSet: Text is NULL!"); assertNotNull(text, "npcTextSet: Text is NULL!");
strncpy(npc->text, text, LANGUAGE_STRING_KEY_LENGTH_MAX); strncpy(npc->text.text, text, LANGUAGE_STRING_KEY_LENGTH_MAX);
} }

View File

@ -11,6 +11,15 @@
typedef struct { typedef struct {
char_t name[LANGUAGE_STRING_KEY_LENGTH_MAX+1]; char_t name[LANGUAGE_STRING_KEY_LENGTH_MAX+1];
char_t text[LANGUAGE_STRING_KEY_LENGTH_MAX+1]; char_t text[LANGUAGE_STRING_KEY_LENGTH_MAX+1];
} npctext_t;
typedef struct {
uint8_t nothing;
} npcconversation_t;
typedef union {
npctext_t text;
npcconversation_t conversation;
} npc_t; } npc_t;
/** /**

View File

@ -7,14 +7,24 @@
#include "sign.h" #include "sign.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h"
void signInit(sign_t *sign) { void signInit(sign_t *sign) {
assertNotNull(sign, "signInit: Sign is NULL!"); assertNotNull(sign, "signInit: Sign is NULL!");
sign->text[0] = '\0'; memorySet(sign, 0, sizeof(sign_t));
} }
void signTextSet(sign_t *sign, const char_t *text) { void signTextAppend(sign_t *sign, const char_t *text) {
assertNotNull(sign, "signTextSet: Sign is NULL!"); assertNotNull(sign, "signConversationAppend: Sign is NULL!");
assertNotNull(text, "signTextSet: Text is NULL!"); assertNotNull(text, "signConversationAppend: Text is NULL!");
strncpy(sign->text, text, LANGUAGE_STRING_KEY_LENGTH_MAX);
// Find the first empty slot
for(int32_t i = 0; i < SIGN_TEXT_COUNT_MAX; i++) {
if(sign->texts[i][0] == '\0') {
strncpy(sign->texts[i], text, LANGUAGE_STRING_KEY_LENGTH_MAX);
return;
}
}
assertUnreachable("signConversationAppend: No empty slots found!");
} }

View File

@ -8,8 +8,10 @@
#pragma once #pragma once
#include "locale/language.h" #include "locale/language.h"
#define SIGN_TEXT_COUNT_MAX 32
typedef struct { typedef struct {
char_t text[LANGUAGE_STRING_KEY_LENGTH_MAX+1]; char_t texts[SIGN_TEXT_COUNT_MAX][LANGUAGE_STRING_KEY_LENGTH_MAX+1];
} sign_t; } sign_t;
/** /**
@ -20,9 +22,9 @@ typedef struct {
void signInit(sign_t *sign); void signInit(sign_t *sign);
/** /**
* Sets the text of a sign. * Appends text to the sign's conversation.
* *
* @param sign Sign to set text for. * @param sign Sign to append conversation to.
* @param text Text to set. * @param text Text to append.
*/ */
void signTextSet(sign_t *sign, const char_t *text); void signTextAppend(sign_t *sign, const char_t *text);