- Mesaj
- 219
- Çözümler
- 6
- Beğeni
- 564
- Puan
- 819
- Ticaret Puanı
- 0
Eskiden IRC'de Kaos soru-cevap botu isminde bir etkinlik botu vardı, onu metin2'ye uyarladım. OX gibi mini bir event olarak düşünebilirsiniz, her soru birden fazla cevap destekler ve her cevap için katılımcılar + puan kazanır ve en çok puana ulaşanda belirlediğiniz ödülü alır.
Aşağıdaki diff patch dosyasındaki değişiklikleri yaparak ekleyebilirsiniz.
locale kısmı eksik kalmış, locale/../locale_game.txt dosyasına ekleyin:
Sublime text ya da benzeri modern bir IDE ile ya da online olarak
Event komutları:
/quiz_manage <start/end/scores>
Aşağıdaki diff patch dosyasındaki değişiklikleri yaparak ekleyebilirsiniz.
Linkleri görebilmek için
giriş yap veya kayıt ol.
Diff:
From 4d2d1a51d48e4e9ca2fcf66e5829dbef727fac45 Mon Sep 17 00:00:00 2001
From: mq1n <[email protected]>
Date: Thu, 17 Jul 2025 20:45:52 +0300
Subject: [PATCH] quiz
---
client/pack/root/colorinfo.py | 1 +
client/pack/root/uichat.py | 111 +++-
.../Srcs/Client/UserInterface/Packet.h | 1 +
.../Srcs/Client/UserInterface/PythonChat.cpp | 1 +
.../Client/UserInterface/PythonChatModule.cpp | 1 +
.../PythonNetworkStreamPhaseGame.cpp | 1 +
s3ll_server/Srcs/Server/common/length.h | 1 +
s3ll_server/Srcs/Server/game/src/QuizShow.cpp | 622 ++++++++++++++++++
s3ll_server/Srcs/Server/game/src/QuizShow.h | 80 +++
s3ll_server/Srcs/Server/game/src/char.cpp | 3 +
s3ll_server/Srcs/Server/game/src/cmd.cpp | 8 +
.../Srcs/Server/game/src/cmd_general.cpp | 11 +
s3ll_server/Srcs/Server/game/src/cmd_gm.cpp | 9 +
s3ll_server/Srcs/Server/game/src/game.vcxproj | 2 +
.../Srcs/Server/game/src/game.vcxproj.filters | 6 +
.../Srcs/Server/game/src/input_main.cpp | 18 +
s3ll_server/Srcs/Server/game/src/main.cpp | 2 +
.../srv1/share/locale/germany/quizshow.lua | 57 ++
18 files changed, 924 insertions(+), 11 deletions(-)
create mode 100644 s3ll_server/Srcs/Server/game/src/QuizShow.cpp
create mode 100644 s3ll_server/Srcs/Server/game/src/QuizShow.h
create mode 100644 s3ll_svfiles/main/srv1/share/locale/germany/quizshow.lua
diff --git a/client/pack/root/colorinfo.py b/client/pack/root/colorinfo.py
index 5898eace..56a0f1ec 100644
--- a/client/pack/root/colorinfo.py
+++ b/client/pack/root/colorinfo.py
@@ -3,6 +3,7 @@ CHAT_RGB_INFO = (255, 200, 200) #1
CHAT_RGB_NOTICE = (255, 230, 186) #2
CHAT_RGB_PARTY = (0, 255, 228) #3
CHAT_RGB_GUILD = (253, 255, 124) #4
+CHAT_RGB_QUIZ = (255, 255, 255) #5
CHAT_RGB_COMMAND = (167, 255, 212) #5
CHAT_RGB_SHOUT = (167, 255, 212) #6
CHAT_RGB_WHISPER = (74, 225, 74) #7
diff --git a/client/pack/root/uichat.py b/client/pack/root/uichat.py
index e2a3980b..00a8347e 100644
--- a/client/pack/root/uichat.py
+++ b/client/pack/root/uichat.py
@@ -112,10 +112,13 @@ class ChatModeButton(ui.Window):
## ChatLine
class ChatLine(ui.EditLine):
- CHAT_MODE_NAME = { chat.CHAT_TYPE_TALKING : localeInfo.CHAT_NORMAL,
- chat.CHAT_TYPE_PARTY : localeInfo.CHAT_PARTY,
- chat.CHAT_TYPE_GUILD : localeInfo.CHAT_GUILD,
- chat.CHAT_TYPE_SHOUT : localeInfo.CHAT_SHOUT, }
+ CHAT_MODE_NAME = {
+ chat.CHAT_TYPE_TALKING : localeInfo.CHAT_NORMAL,
+ chat.CHAT_TYPE_PARTY : localeInfo.CHAT_PARTY,
+ chat.CHAT_TYPE_GUILD : localeInfo.CHAT_GUILD,
+ chat.CHAT_TYPE_SHOUT : localeInfo.CHAT_SHOUT,
+ chat.CHAT_TYPE_QUIZ : localeInfo.CHAT_QUIZ,
+ }
def __init__(self):
ui.EditLine.__init__(self)
@@ -138,6 +141,14 @@ class ChatLine(ui.EditLine):
self.lastSentencePos = 0
def SetChatMode(self, mode):
+ # Handle quiz mode transitions
+ if self.chatMode != chat.CHAT_TYPE_QUIZ and mode == chat.CHAT_TYPE_QUIZ:
+ # Entering quiz mode
+ net.SendChatPacket("/enter_quiz_spec", chat.CHAT_TYPE_TALKING)
+ elif self.chatMode == chat.CHAT_TYPE_QUIZ and mode != chat.CHAT_TYPE_QUIZ:
+ # Leaving quiz mode
+ net.SendChatPacket("/leave_quiz_spec", chat.CHAT_TYPE_TALKING)
+
self.chatMode = mode
def GetChatMode(self):
@@ -150,6 +161,11 @@ class ChatLine(ui.EditLine):
self.SetEndPosition()
elif chat.CHAT_TYPE_PARTY == self.GetChatMode():
+ self.SetChatMode(chat.CHAT_TYPE_QUIZ)
+ self.SetText("&")
+ self.SetEndPosition()
+
+ elif chat.CHAT_TYPE_QUIZ == self.GetChatMode():
self.SetChatMode(chat.CHAT_TYPE_GUILD)
self.SetText("%")
self.SetEndPosition()
@@ -199,6 +215,9 @@ class ChatLine(ui.EditLine):
elif chat.CHAT_TYPE_GUILD == self.GetChatMode():
self.SetText("%")
self.SetEndPosition()
+ elif chat.CHAT_TYPE_QUIZ == self.GetChatMode():
+ self.SetText("&")
+ self.SetEndPosition()
elif chat.CHAT_TYPE_SHOUT == self.GetChatMode():
self.SetText("!")
self.SetEndPosition()
@@ -248,6 +267,20 @@ class ChatLine(ui.EditLine):
self.lastShoutTime = app.GetTime()
+ def __SendQuizChatPacket(self, text):
+
+ if 1 == len(text):
+ self.RunCloseEvent()
+ return
+
+ # if not net.IsQuizEnabled():
+ # chat.AppendChat(chat.CHAT_TYPE_INFO, localeInfo.CHAT_QUIZ_NOT_ENABLED)
+ # self.__ResetChat()
+ # return
+
+ self.__SendChatPacket(text[1:], chat.CHAT_TYPE_QUIZ)
+ self.__ResetChat()
+
def __SendTalkingChatPacket(self, text):
self.__SendChatPacket(text, chat.CHAT_TYPE_TALKING)
self.__ResetChat()
@@ -274,6 +307,9 @@ class ChatLine(ui.EditLine):
elif '%' == text[0]:
self.overTextLine.SetText("%")
self.overTextLine.Show()
+ elif '&' == text[0]:
+ self.overTextLine.SetText("&")
+ self.overTextLine.Show()
elif '!' == text[0]:
self.overTextLine.SetText("!")
self.overTextLine.Show()
@@ -346,6 +382,8 @@ class ChatLine(ui.EditLine):
self.__SendGuildChatPacket(text)
elif '!' == text[0]:
self.__SendShoutChatPacket(text)
+ elif '&' == text[0]:
+ self.__SendQuizChatPacket(text)
else:
self.__SendTalkingChatPacket(text)
else:
@@ -624,6 +662,7 @@ class ChatWindow(ui.Window):
chat.CHAT_TYPE_NOTICE : colorInfo.CHAT_RGB_NOTICE,
chat.CHAT_TYPE_PARTY : colorInfo.CHAT_RGB_PARTY,
chat.CHAT_TYPE_GUILD : colorInfo.CHAT_RGB_GUILD,
+ chat.CHAT_TYPE_QUIZ : colorInfo.CHAT_RGB_QUIZ,
chat.CHAT_TYPE_COMMAND : colorInfo.CHAT_RGB_COMMAND,
chat.CHAT_TYPE_SHOUT : colorInfo.CHAT_RGB_SHOUT,
chat.CHAT_TYPE_WHISPER : colorInfo.CHAT_RGB_WHISPER,
@@ -840,13 +879,24 @@ class ChatWindow(ui.Window):
class ChatLogWindow(ui.Window):
BLOCK_WIDTH = 32
- CHAT_MODE_NAME = [ localeInfo.CHAT_NORMAL, localeInfo.CHAT_PARTY, localeInfo.CHAT_GUILD, localeInfo.CHAT_SHOUT, localeInfo.CHAT_INFORMATION, localeInfo.CHAT_NOTICE, ]
- CHAT_MODE_INDEX = [ chat.CHAT_TYPE_TALKING,
- chat.CHAT_TYPE_PARTY,
- chat.CHAT_TYPE_GUILD,
- chat.CHAT_TYPE_SHOUT,
- chat.CHAT_TYPE_INFO,
- chat.CHAT_TYPE_NOTICE, ]
+ CHAT_MODE_NAME = [
+ localeInfo.CHAT_NORMAL,
+ localeInfo.CHAT_PARTY,
+ localeInfo.CHAT_GUILD,
+ localeInfo.CHAT_SHOUT,
+ localeInfo.CHAT_INFORMATION,
+ localeInfo.CHAT_NOTICE,
+ localeInfo.CHAT_QUIZ,
+ ]
+ CHAT_MODE_INDEX = [
+ chat.CHAT_TYPE_TALKING,
+ chat.CHAT_TYPE_PARTY,
+ chat.CHAT_TYPE_GUILD,
+ chat.CHAT_TYPE_SHOUT,
+ chat.CHAT_TYPE_INFO,
+ chat.CHAT_TYPE_NOTICE,
+ chat.CHAT_TYPE_QUIZ,
+ ]
if app.ENABLE_DICE_SYSTEM:
CHAT_MODE_NAME.append(localeInfo.CHAT_DICE_INFO)
@@ -1026,14 +1076,32 @@ class ChatLogWindow(ui.Window):
if self.allChatMode:
return
+ # Check if quiz mode was active before switching to all chat mode
+ quiz_was_active = False
+ quiz_button_index = None
+ for i, chat_mode in enumerate(self.CHAT_MODE_INDEX):
+ if chat_mode == chat.CHAT_TYPE_QUIZ:
+ quiz_button_index = i
+ break
+
+ if quiz_button_index is not None and quiz_button_index < len(self.modeButtonList):
+ quiz_button = self.modeButtonList[quiz_button_index]
+ quiz_was_active = quiz_button.IsDown()
+
self.allChatMode = True
for i in self.CHAT_MODE_INDEX:
chat.EnableChatMode(self.chatID, i)
for btn in self.modeButtonList:
btn.SetUp()
+
+ # If quiz mode was active, send leave command when switching to all chat mode
+ if quiz_was_active:
+ net.SendChatPacket("/leave_quiz_spec", chat.CHAT_TYPE_TALKING)
def ToggleChatMode(self, mode):
+ was_all_chat_mode = self.allChatMode
+
if self.allChatMode:
self.allChatMode = False
for i in self.CHAT_MODE_INDEX:
@@ -1043,6 +1111,27 @@ class ChatLogWindow(ui.Window):
else:
chat.ToggleChatMode(self.chatID, mode)
+
+ # Handle quiz mode transitions for ChatLogWindow based on button state
+ if mode == chat.CHAT_TYPE_QUIZ:
+ # Find the quiz button (index 6 in CHAT_MODE_INDEX)
+ quiz_button_index = None
+ for i, chat_mode in enumerate(self.CHAT_MODE_INDEX):
+ if chat_mode == chat.CHAT_TYPE_QUIZ:
+ quiz_button_index = i
+ break
+
+ if quiz_button_index is not None and quiz_button_index < len(self.modeButtonList):
+ quiz_button = self.modeButtonList[quiz_button_index]
+ if quiz_button.IsDown():
+ # Button is pressed down - entering quiz mode
+ net.SendChatPacket("/enter_quiz_spec", chat.CHAT_TYPE_TALKING)
+ elif was_all_chat_mode:
+ # Coming from all chat mode and quiz button is now active - entering quiz mode
+ net.SendChatPacket("/enter_quiz_spec", chat.CHAT_TYPE_TALKING)
+ else:
+ # Button is up - leaving quiz mode
+ net.SendChatPacket("/leave_quiz_spec", chat.CHAT_TYPE_TALKING)
def SetSize(self, width, height):
self.imgCenter.SetRenderingRect(0.0, 0.0, float((width - self.BLOCK_WIDTH*2) - self.BLOCK_WIDTH) / self.BLOCK_WIDTH, 0.0)
diff --git a/s3ll_client/Srcs/Client/UserInterface/Packet.h b/s3ll_client/Srcs/Client/UserInterface/Packet.h
index a1b2e37a..b8a1cce1 100644
--- a/s3ll_client/Srcs/Client/UserInterface/Packet.h
+++ b/s3ll_client/Srcs/Client/UserInterface/Packet.h
@@ -1365,6 +1365,7 @@ enum EChatType
CHAT_TYPE_NOTICE,
CHAT_TYPE_PARTY,
CHAT_TYPE_GUILD,
+ CHAT_TYPE_QUIZ,
CHAT_TYPE_COMMAND,
CHAT_TYPE_SHOUT,
CHAT_TYPE_WHISPER,
diff --git a/s3ll_client/Srcs/Client/UserInterface/PythonChat.cpp b/s3ll_client/Srcs/Client/UserInterface/PythonChat.cpp
index 4ee94a91..be862145 100644
--- a/s3ll_client/Srcs/Client/UserInterface/PythonChat.cpp
+++ b/s3ll_client/Srcs/Client/UserInterface/PythonChat.cpp
@@ -633,6 +633,7 @@ void CPythonChat::__Initialize()
m_akD3DXClrChat[CHAT_TYPE_NOTICE] = D3DXCOLOR(1.0f, 0.902f, 0.730f, 1.0f);
m_akD3DXClrChat[CHAT_TYPE_PARTY] = D3DXCOLOR(0.542f, 1.0f, 0.949f, 1.0f);
m_akD3DXClrChat[CHAT_TYPE_GUILD] = D3DXCOLOR(0.906f, 0.847f, 1.0f, 1.0f);
+ m_akD3DXClrChat[CHAT_TYPE_QUIZ] = D3DXCOLOR(0.658f, 1.0f, 0.835f, 1.0f);
m_akD3DXClrChat[CHAT_TYPE_COMMAND] = D3DXCOLOR(0.658f, 1.0f, 0.835f, 1.0f);
m_akD3DXClrChat[CHAT_TYPE_SHOUT] = D3DXCOLOR(0.658f, 1.0f, 0.835f, 1.0f);
m_akD3DXClrChat[CHAT_TYPE_WHISPER] = D3DXCOLOR(0xff4AE14A);
diff --git a/s3ll_client/Srcs/Client/UserInterface/PythonChatModule.cpp b/s3ll_client/Srcs/Client/UserInterface/PythonChatModule.cpp
index 1dd99350..cd8cc8a0 100644
--- a/s3ll_client/Srcs/Client/UserInterface/PythonChatModule.cpp
+++ b/s3ll_client/Srcs/Client/UserInterface/PythonChatModule.cpp
@@ -503,6 +503,7 @@ void initChat()
PyModule_AddIntConstant(poModule, "CHAT_TYPE_NOTICE", CHAT_TYPE_NOTICE);
PyModule_AddIntConstant(poModule, "CHAT_TYPE_PARTY", CHAT_TYPE_PARTY);
PyModule_AddIntConstant(poModule, "CHAT_TYPE_GUILD", CHAT_TYPE_GUILD);
+ PyModule_AddIntConstant(poModule, "CHAT_TYPE_QUIZ", CHAT_TYPE_QUIZ);
PyModule_AddIntConstant(poModule, "CHAT_TYPE_COMMAND", CHAT_TYPE_COMMAND);
PyModule_AddIntConstant(poModule, "CHAT_TYPE_SHOUT", CHAT_TYPE_SHOUT);
PyModule_AddIntConstant(poModule, "CHAT_TYPE_WHISPER", CHAT_TYPE_WHISPER);
diff --git a/s3ll_client/Srcs/Client/UserInterface/PythonNetworkStreamPhaseGame.cpp b/s3ll_client/Srcs/Client/UserInterface/PythonNetworkStreamPhaseGame.cpp
index 35398bf0..12439847 100644
--- a/s3ll_client/Srcs/Client/UserInterface/PythonNetworkStreamPhaseGame.cpp
+++ b/s3ll_client/Srcs/Client/UserInterface/PythonNetworkStreamPhaseGame.cpp
@@ -1404,6 +1404,7 @@ bool CPythonNetworkStream::RecvChatPacket()
case CHAT_TYPE_TALKING:
case CHAT_TYPE_PARTY:
case CHAT_TYPE_GUILD:
+ case CHAT_TYPE_QUIZ:
case CHAT_TYPE_SHOUT:
case CHAT_TYPE_WHISPER:
{
diff --git a/s3ll_server/Srcs/Server/common/length.h b/s3ll_server/Srcs/Server/common/length.h
index 8d5b3852..ff25a2cf 100644
--- a/s3ll_server/Srcs/Server/common/length.h
+++ b/s3ll_server/Srcs/Server/common/length.h
@@ -309,6 +309,7 @@ enum EChatType
CHAT_TYPE_NOTICE,
CHAT_TYPE_PARTY,
CHAT_TYPE_GUILD,
+ CHAT_TYPE_QUIZ,
CHAT_TYPE_COMMAND,
CHAT_TYPE_SHOUT,
CHAT_TYPE_WHISPER,
diff --git a/s3ll_server/Srcs/Server/game/src/QuizShow.cpp b/s3ll_server/Srcs/Server/game/src/QuizShow.cpp
new file mode 100644
index 00000000..20959427
--- /dev/null
+++ b/s3ll_server/Srcs/Server/game/src/QuizShow.cpp
@@ -0,0 +1,622 @@
+#include "stdafx.h"
+#include "QuizShow.h"
+#include "char.h"
+#include "char_manager.h"
+#include "desc_manager.h"
+#include "questmanager.h"
+#include "log.h"
+#include "lua_incl.h"
+#include "locale_service.h"
+#include "event.h"
+#include "config.h"
+#include "item_manager.h"
+#include <boost/algorithm/string.hpp>
+#include <boost/regex.hpp>
+#include <random>
+#include <filesystem>
+#include <fmt/format.h>
+
+EVENTINFO(quiz_show_event_info)
+{
+};
+EVENTFUNC(quiz_show_event_func)
+{
+ if (!event || !event->info)
+ return 0;
+
+ auto& manager = CQuizShowManager::instance();
+
+ // Let the manager handle the event tick
+ manager.ProcessEvent();
+
+ // Continue the event if quiz is still running
+ return manager.IsRunning() ? PASSES_PER_SEC(1) : 0;
+}
+
+CQuizShowManager::CQuizShowManager()
+{
+ m_isRunning = false;
+ m_currentQuestionIndex = -1;
+ m_event = nullptr;
+ m_state = QUIZ_IDLE;
+ m_stateTimer = 0;
+ m_lastNoticeTime = 0;
+}
+CQuizShowManager::~CQuizShowManager()
+{
+ Clear();
+}
+
+void CQuizShowManager::Clear()
+{
+ m_questions.clear();
+ m_rewards.clear();
+ m_map_scores.clear();
+ m_found_answers.clear();
+ m_watchers.clear();
+
+ m_isRunning = false;
+ m_currentQuestionIndex = -1;
+ m_state = QUIZ_IDLE;
+ m_stateTimer = 0;
+ m_lastNoticeTime = 0;
+ m_event = nullptr;
+}
+
+void CQuizShowManager::StartEvent()
+{
+ if (m_isRunning)
+ return;
+
+ Clear();
+ m_isRunning = true;
+ LoadQuestions("quizshow.lua");
+
+ if (m_questions.empty())
+ {
+ BroadcastNotice("Quiz show questions file not found.");
+ EndEvent();
+ return;
+ }
+
+ std::random_device rd;
+ std::mt19937 g(rd());
+ std::shuffle(m_questions.begin(), m_questions.end(), g);
+
+ BroadcastNotice("The quiz show has started! Please send your answers in the format '&answer'.");
+
+ // Initialize state for first question
+ m_currentQuestionIndex = -1;
+ m_state = QUIZ_PREPARING;
+ m_stateTimer = 0;
+ m_lastNoticeTime = 0;
+
+ // Start the single continuous event
+ auto info = AllocEventInfo<quiz_show_event_info>();
+ m_event = event_create(quiz_show_event_func, info, PASSES_PER_SEC(1));
+}
+
+void CQuizShowManager::EndEvent()
+{
+ if (!m_isRunning)
+ return;
+
+ if (m_event)
+ {
+ event_cancel(&m_event);
+ m_event = nullptr;
+ }
+
+ GiveRewardToWinner();
+ ShowScores();
+ BroadcastNotice("The quiz show has ended. Thank you for participating!");
+ Clear();
+}
+
+void CQuizShowManager::ClearEvent()
+{
+ if (m_event)
+ {
+ event_cancel(&m_event);
+ }
+ m_event = nullptr;
+}
+
+void CQuizShowManager::LoadQuestions(const std::string& filename)
+{
+ lua_State* L = quest::CQuestManager::instance().GetLuaState();
+ if (!L)
+ {
+ sys_err("Lua state is not initialized.");
+ return;
+ }
+
+ char script[256];
+ std::snprintf(script, sizeof(script), "%s/%s", LocaleService_GetBasePath().c_str(), filename.c_str());
+ if (!std::filesystem::exists(script))
+ {
+ sys_err("Quiz show script file not found: %s", script);
+ return;
+ }
+
+ if (lua_dofile(L, script))
+ {
+ sys_err("Cannot dofile quizshow script: %s", script);
+ return;
+ }
+
+ // Load questions
+ lua_getglobal(L, "quiz_questions");
+ if (lua_isnil(L, -1))
+ {
+ sys_err("quiz_questions table not found in %s", filename.c_str());
+ lua_pop(L, 1);
+ return;
+ }
+
+ if (lua_istable(L, -1))
+ {
+ const size_t tableSize = static_cast<size_t>(luaL_getn(L, -1));
+ for (int i = 1; i <= tableSize; ++i)
+ {
+ lua_rawgeti(L, -1, i);
+ if (lua_istable(L, -1))
+ {
+ TQuizQuestion q;
+
+ lua_pushstring(L, "q");
+ lua_gettable(L, -2);
+ if (lua_isstring(L, -1)) {
+ q.question = lua_tostring(L, -1);
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "a");
+ lua_gettable(L, -2);
+ if (lua_istable(L, -1)) {
+ int answers_size = luaL_getn(L, -1);
+ for (int j = 1; j <= answers_size; ++j) {
+ lua_rawgeti(L, -1, j);
+ if (lua_isstring(L, -1)) {
+ q.answers.push_back(lua_tostring(L, -1));
+ }
+ lua_pop(L, 1);
+ }
+ }
+ lua_pop(L, 1);
+
+ m_questions.push_back(q);
+ }
+ lua_pop(L, 1);
+ }
+ }
+ lua_pop(L, 1);
+
+ // Load rewards
+ LoadRewards();
+}
+
+void CQuizShowManager::LoadRewards()
+{
+ lua_State* L = quest::CQuestManager::instance().GetLuaState();
+ if (!L)
+ {
+ sys_err("Lua state is not initialized.");
+ return;
+ }
+
+ lua_getglobal(L, "quiz_rewards");
+ if (lua_isnil(L, -1))
+ {
+ sys_log(0, "quiz_rewards table not found, no rewards will be given");
+ lua_pop(L, 1);
+ return;
+ }
+
+ if (lua_istable(L, -1))
+ {
+ const size_t tableSize = static_cast<size_t>(luaL_getn(L, -1));
+ for (int i = 1; i <= tableSize; ++i)
+ {
+ lua_rawgeti(L, -1, i);
+ if (lua_istable(L, -1))
+ {
+ TQuizReward reward;
+
+ lua_pushstring(L, "vnum");
+ lua_gettable(L, -2);
+ if (lua_isnumber(L, -1)) {
+ reward.vnum = static_cast<uint32_t>(lua_tonumber(L, -1));
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "count");
+ lua_gettable(L, -2);
+ if (lua_isnumber(L, -1)) {
+ reward.count = static_cast<uint32_t>(lua_tonumber(L, -1));
+ } else {
+ reward.count = 1;
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "name");
+ lua_gettable(L, -2);
+ if (lua_isstring(L, -1)) {
+ reward.name = lua_tostring(L, -1);
+ }
+ lua_pop(L, 1);
+
+ if (reward.vnum > 0) {
+ m_rewards.push_back(reward);
+ }
+ }
+ lua_pop(L, 1);
+ }
+ }
+ lua_pop(L, 1);
+
+ if (!m_rewards.empty())
+ {
+ std::string reward_msg = "Quiz show rewards: ";
+
+ for (size_t i = 0; i < m_rewards.size(); ++i)
+ {
+ if (i > 0)
+ reward_msg += ", ";
+
+ if (!m_rewards[i].name.empty())
+ {
+ reward_msg += fmt::format("{}x {}", m_rewards[i].count, m_rewards[i].name);
+ }
+ else
+ {
+ reward_msg += fmt::format("{}x Item#{}", m_rewards[i].count, m_rewards[i].vnum);
+ }
+ }
+
+ BroadcastNotice(reward_msg.c_str());
+ }
+}
+
+void CQuizShowManager::ProcessEvent()
+{
+ if (!m_isRunning)
+ return;
+
+ m_stateTimer++;
+
+ switch (m_state)
+ {
+ case QUIZ_PREPARING:
+ {
+ // Wait 3 seconds before first question
+ if (m_stateTimer >= 3)
+ {
+ SendQuestion();
+ }
+ break;
+ }
+
+ case QUIZ_ASKING:
+ {
+ // Send periodic reminders about remaining time
+ int remaining = 30 - m_stateTimer;
+ if (remaining > 0 && (remaining == 20 || remaining == 10 || remaining == 5))
+ {
+ if (m_lastNoticeTime != remaining)
+ {
+ BroadcastNotice(fmt::format("Time remaining: {} seconds", remaining).c_str());
+ m_lastNoticeTime = remaining;
+ }
+ }
+
+ // Check if all answers found
+ if (m_found_answers.size() == m_questions[m_currentQuestionIndex].answers.size())
+ {
+ // All answers found - transition to reveal immediately but wait for timer
+ if (m_state != QUIZ_REVEAL)
+ {
+ m_state = QUIZ_REVEAL;
+ BroadcastNotice("All answers have been found!");
+ ShowScores();
+ BroadcastNotice("Next question will be asked in 15 seconds. Please prepare your answers!");
+ m_stateTimer = 0; // Reset timer for reveal phase
+ }
+ }
+ // Time's up
+ else if (m_stateTimer >= 30)
+ {
+ EndRound();
+ }
+ break;
+ }
+
+ case QUIZ_REVEAL:
+ {
+ // Send countdown notices for next question
+ int remaining = 15 - m_stateTimer;
+ if (remaining > 0 && (remaining == 10 || remaining == 5))
+ {
+ if (m_lastNoticeTime != remaining)
+ {
+ BroadcastNotice(fmt::format("Next question in {} seconds", remaining).c_str());
+ m_lastNoticeTime = remaining;
+ }
+ }
+
+ // Time to send next question
+ if (m_stateTimer >= 15)
+ {
+ SendQuestion();
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void CQuizShowManager::SendQuestion()
+{
+ if (!m_isRunning || m_questions.empty())
+ {
+ EndEvent();
+ return;
+ }
+
+ m_currentQuestionIndex++;
+ m_found_answers.clear();
+
+ if (m_currentQuestionIndex >= m_questions.size())
+ {
+ EndEvent();
+ return;
+ }
+
+ const auto& q = m_questions[m_currentQuestionIndex];
+ BroadcastNotice(fmt::format("Question #{0}: {1} ({2} answers)", m_currentQuestionIndex + 1, q.question, q.answers.size()).c_str());
+
+ m_state = QUIZ_ASKING;
+ m_stateTimer = 0;
+ m_lastNoticeTime = 0;
+}
+
+void CQuizShowManager::EndRound()
+{
+ m_state = QUIZ_REVEAL;
+
+ std::string reveal_msg = "Time's up! ";
+ int unfound_count = 0;
+
+ for (const auto& answer : m_questions[m_currentQuestionIndex].answers)
+ {
+ if (m_found_answers.find(NormalizeAnswer(answer)) == m_found_answers.end())
+ {
+ unfound_count++;
+ }
+ }
+
+ if (unfound_count == 0)
+ reveal_msg = "All answers have been found!";
+ else
+ reveal_msg += fmt::format("Unfound answers: {} out of {}", unfound_count, m_questions[m_currentQuestionIndex].answers.size());
+
+ BroadcastNotice(reveal_msg.c_str());
+ ShowScores();
+ BroadcastNotice("Next question will be asked in 15 seconds. Please prepare your answers!");
+
+ // Reset timer for reveal phase - the single event will handle the transition
+ m_stateTimer = 0;
+ m_lastNoticeTime = 0;
+}
+
+
+void CQuizShowManager::HandleAnswer(LPCHARACTER ch, const std::string& answer)
+{
+ if (!m_isRunning || m_state != QUIZ_ASKING)
+ return;
+
+ std::string normalized_answer = NormalizeAnswer(answer);
+
+ for (const auto& correct_answer : m_questions[m_currentQuestionIndex].answers)
+ {
+ if (normalized_answer == NormalizeAnswer(correct_answer))
+ {
+ if (m_found_answers.find(normalized_answer) == m_found_answers.end())
+ {
+ m_found_answers.insert(normalized_answer);
+ m_map_scores[ch->GetPlayerID()]++;
+ BroadcastNotice(fmt::format("{0} has correctly answered '{1}'! (+1 point)", ch->GetName(), correct_answer).c_str());
+ }
+ else
+ {
+ ch->ChatPacket(CHAT_TYPE_INFO, "That answer has already been found.");
+ }
+ return;
+ }
+ }
+}
+
+std::string CQuizShowManager::NormalizeAnswer(const std::string& str)
+{
+ std::string temp = str;
+ boost::algorithm::to_lower(temp);
+ boost::regex re("[^a-z0-9]");
+ return boost::regex_replace(temp, re, "");
+}
+
+void CQuizShowManager::HandleCommand(LPCHARACTER ch, const std::string& command)
+{
+ if (!ch->IsGM())
+ return;
+
+ std::vector<std::string> tokens;
+ boost::split(tokens, command, boost::is_any_of(" "));
+
+ if (tokens.empty())
+ return;
+
+ if (tokens[0] == "start")
+ {
+ StartEvent();
+ }
+ else if (tokens[0] == "end")
+ {
+ EndEvent();
+ }
+ else if (tokens[0] == "scores")
+ {
+ ShowScores(ch);
+ }
+}
+
+void CQuizShowManager::ShowScores(LPCHARACTER ch)
+{
+ std::string score_message = "--- Quiz Show Scores ---";
+
+ if (m_map_scores.empty())
+ {
+ score_message += "\nNo players have scored yet.";
+ }
+ else
+ {
+ std::vector<std::pair<uint32_t, int>> sorted_scores(m_map_scores.begin(), m_map_scores.end());
+ std::sort(sorted_scores.begin(), sorted_scores.end(), [](const auto& a, const auto& b) {
+ return a.second > b.second;
+ });
+
+ int displayed_count = 0;
+ for (const auto& score_pair : sorted_scores)
+ {
+ if (displayed_count >= 10)
+ break;
+
+ LPCHARACTER player = CHARACTER_MANAGER::instance().FindByPID(score_pair.first);
+ if (player)
+ {
+ score_message += fmt::format("\n{0}: {1} points", player->GetName(), score_pair.second);
+ displayed_count++;
+ }
+ }
+
+ if (sorted_scores.size() > 10)
+ {
+ score_message += fmt::format("\n... and {} more players", sorted_scores.size() - 10);
+ }
+ }
+
+ if (ch)
+ ch->ChatPacket(CHAT_TYPE_INFO, score_message.c_str());
+ else
+ BroadcastNotice(score_message.c_str());
+}
+
+void CQuizShowManager::GiveRewardToWinner()
+{
+ if (m_rewards.empty() || m_map_scores.empty())
+ return;
+
+ uint32_t winner_pid = 0;
+ int max_score = 0;
+
+ for (const auto& score_pair : m_map_scores)
+ {
+ if (score_pair.second > max_score)
+ {
+ max_score = score_pair.second;
+ winner_pid = score_pair.first;
+ }
+ }
+
+ if (winner_pid != 0)
+ {
+ LPCHARACTER winner = CHARACTER_MANAGER::instance().FindByPID(winner_pid);
+ if (winner)
+ {
+ // Give all rewards to the winner
+ for (const auto& reward : m_rewards)
+ {
+ if (reward.vnum > 2)
+ {
+ winner->AutoGiveItem(reward.vnum, reward.count);
+ }
+ else if (reward.vnum == 2) // Special case for EXP
+ {
+ winner->PointChange(POINT_EXP, reward.count, true);
+ }
+ else if (reward.vnum == 1) // Special case for yang
+ {
+ winner->PointChange(POINT_GOLD, reward.count, true);
+ }
+ else
+ {
+ sys_err("CQuizShowManager::GiveRewardToWinner : Invalid reward vnum %u", reward.vnum);
+ }
+ }
+
+ const auto buffer = fmt::format("The winner is {0} with {1} points! Congratulations!", winner->GetName(), max_score);
+ BroadcastNotice(buffer.c_str(), true);
+ }
+ else
+ {
+ sys_err("CQuizShowManager::GiveRewardToWinner : Winner character not found (PID: %u)", winner_pid);
+ }
+ }
+}
+
+// Watcher functionality implementation
+void CQuizShowManager::AddWatcher(uint32_t pid)
+{
+ if (!pid)
+ return;
+
+ if (IsWatcher(pid))
+ return;
+
+ m_watchers.insert(pid);
+
+ LPCHARACTER ch = CHARACTER_MANAGER::instance().FindByPID(pid);
+ if (ch)
+ ch->ChatPacket(CHAT_TYPE_INFO, "You are now watching the quiz show. You will see all answers submitted by players.");
+}
+
+void CQuizShowManager::RemoveWatcher(uint32_t pid)
+{
+ if (!pid)
+ return;
+
+ auto it = m_watchers.find(pid);
+ if (it != m_watchers.end())
+ {
+ m_watchers.erase(it);
+
+ LPCHARACTER ch = CHARACTER_MANAGER::instance().FindByPID(pid);
+ if (ch)
+ ch->ChatPacket(CHAT_TYPE_INFO, "You are no longer watching the quiz show.");
+ }
+}
+
+void CQuizShowManager::BroadcastToWatchers(const std::string& message)
+{
+ // Remove disconnected watchers
+ auto it = m_watchers.begin();
+ while (it != m_watchers.end())
+ {
+ LPCHARACTER watcher = CHARACTER_MANAGER::instance().FindByPID(*it);
+ if (!watcher || !watcher->GetDesc())
+ {
+ it = m_watchers.erase(it);
+ }
+ else
+ {
+ watcher->ChatPacket(CHAT_TYPE_QUIZ, message.c_str());
+ ++it;
+ }
+ }
+}
+
+bool CQuizShowManager::IsWatcher(uint32_t pid) const
+{
+ return pid && m_watchers.find(pid) != m_watchers.end();
+}
\ No newline at end of file
diff --git a/s3ll_server/Srcs/Server/game/src/QuizShow.h b/s3ll_server/Srcs/Server/game/src/QuizShow.h
new file mode 100644
index 00000000..89d7c20f
--- /dev/null
+++ b/s3ll_server/Srcs/Server/game/src/QuizShow.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include <unordered_set>
+
+class CHARACTER;
+
+struct TQuizQuestion
+{
+ std::string question;
+ std::vector<std::string> answers;
+};
+
+struct TQuizReward
+{
+ uint32_t vnum;
+ uint32_t count;
+ std::string name;
+};
+
+enum EQuizState
+{
+ QUIZ_IDLE,
+ QUIZ_PREPARING,
+ QUIZ_ASKING,
+ QUIZ_REVEAL,
+};
+
+class CQuizShowManager : public singleton<CQuizShowManager>
+{
+public:
+ CQuizShowManager();
+ virtual ~CQuizShowManager();
+
+ void StartEvent();
+ void EndEvent();
+ bool IsRunning() const { return m_isRunning; }
+
+ void LoadQuestions(const std::string& filename);
+ void SendQuestion();
+ void EndRound();
+
+ void HandleAnswer(LPCHARACTER ch, const std::string& answer);
+ void HandleCommand(LPCHARACTER ch, const std::string& command);
+
+ void ShowScores(LPCHARACTER ch = nullptr);
+ void ClearEvent();
+ EQuizState GetState() const { return m_state; }
+ void ProcessEvent();
+
+ // Watcher functionality
+ void AddWatcher(uint32_t pid);
+ void RemoveWatcher(uint32_t pid);
+ void BroadcastToWatchers(const std::string& message);
+ bool IsWatcher(uint32_t pid) const;
+
+protected:
+ void Clear();
+ std::string NormalizeAnswer(const std::string& str);
+ void GiveRewardToWinner();
+ void LoadRewards();
+
+private:
+ bool m_isRunning;
+ EQuizState m_state;
+ std::vector<TQuizQuestion> m_questions;
+ std::vector<TQuizReward> m_rewards;
+ int m_currentQuestionIndex;
+ LPEVENT m_event;
+
+ std::unordered_map<uint32_t, int> m_map_scores;
+ std::unordered_set<std::string> m_found_answers;
+ std::unordered_set<uint32_t> m_watchers;
+
+ // Timer management for single event system
+ int m_stateTimer;
+ int m_lastNoticeTime;
+};
\ No newline at end of file
diff --git a/s3ll_server/Srcs/Server/game/src/char.cpp b/s3ll_server/Srcs/Server/game/src/char.cpp
index 7fa25519..39ec7e2b 100644
--- a/s3ll_server/Srcs/Server/game/src/char.cpp
+++ b/s3ll_server/Srcs/Server/game/src/char.cpp
@@ -28,6 +28,7 @@
#include "party.h"
#include "start_position.h"
#include "questmanager.h"
+#include "QuizShow.h"
#include "log.h"
#include "p2p.h"
#include "guild.h"
@@ -1476,6 +1477,8 @@ void CHARACTER::Disconnect(const char * c_pszReason)
GetParty()->UpdateOfflineState(GetPlayerID());
}
+ CQuizShowManager::instance().RemoveWatcher(GetPlayerID());
+
marriage::CManager::instance().Logout(this);
// P2P Logout
diff --git a/s3ll_server/Srcs/Server/game/src/cmd.cpp b/s3ll_server/Srcs/Server/game/src/cmd.cpp
index e710e12a..186ac1ef 100644
--- a/s3ll_server/Srcs/Server/game/src/cmd.cpp
+++ b/s3ll_server/Srcs/Server/game/src/cmd.cpp
@@ -59,6 +59,10 @@ ACMD(do_refine_pick);
ACMD(do_refine_tool);
+ACMD(do_quiz_manage);
+ACMD(do_enter_quiz_spec);
+ACMD(do_leave_quiz_spec);
+
ACMD(do_fishing_simul);
ACMD(do_console);
ACMD(do_restart);
@@ -387,6 +391,10 @@ struct command_info cmd_info[] =
{ "refine_tool", do_refine_tool, 0, POS_DEAD, GM_IMPLEMENTOR },
+ { "quiz_manage", do_quiz_manage, 0, POS_DEAD, GM_IMPLEMENTOR },
+ { "enter_quiz_spec", do_enter_quiz_spec, 0, POS_DEAD, GM_PLAYER },
+ { "leave_quiz_spec", do_leave_quiz_spec, 0, POS_DEAD, GM_PLAYER },
+
{ "fish_simul", do_fishing_simul, 0, POS_DEAD, GM_IMPLEMENTOR },
{ "invisible", do_invisibility, 0, POS_DEAD, GM_LOW_WIZARD },
{ "qf", do_qf, 0, POS_DEAD, GM_IMPLEMENTOR },
diff --git a/s3ll_server/Srcs/Server/game/src/cmd_general.cpp b/s3ll_server/Srcs/Server/game/src/cmd_general.cpp
index 9e6fe1e5..46614246 100644
--- a/s3ll_server/Srcs/Server/game/src/cmd_general.cpp
+++ b/s3ll_server/Srcs/Server/game/src/cmd_general.cpp
@@ -17,6 +17,7 @@
#include "pvp.h"
#include "start_position.h"
#include "party.h"
+#include "QuizShow.h"
#include "guild_manager.h"
#include "p2p.h"
#include "dungeon.h"
@@ -2186,6 +2187,16 @@ ACMD(do_cube)
}
}
+ACMD(do_enter_quiz_spec)
+{
+ CQuizShowManager::instance().AddWatcher(ch->GetPlayerID());
+}
+
+ACMD(do_leave_quiz_spec)
+{
+ CQuizShowManager::instance().RemoveWatcher(ch->GetPlayerID());
+}
+
ACMD(do_in_game_mall)
{
if (LC_IsEurope() == true)
diff --git a/s3ll_server/Srcs/Server/game/src/cmd_gm.cpp b/s3ll_server/Srcs/Server/game/src/cmd_gm.cpp
index db0fa67f..4e900be1 100644
--- a/s3ll_server/Srcs/Server/game/src/cmd_gm.cpp
+++ b/s3ll_server/Srcs/Server/game/src/cmd_gm.cpp
@@ -15,6 +15,7 @@
#include "guild_manager.h"
#include "p2p.h"
#include "buffer_manager.h"
+#include "QuizShow.h"
#include "fishing.h"
#include "mining.h"
#include "questmanager.h"
@@ -4709,6 +4710,14 @@ ACMD (do_use_item)
}
}
+ACMD(do_quiz_manage)
+{
+ char arg1[256];
+ one_argument(argument, arg1, sizeof(arg1));
+
+ CQuizShowManager::instance().HandleCommand(ch, arg1);
+}
+
ACMD (do_clear_affect)
{
ch->ClearAffect(true);
diff --git a/s3ll_server/Srcs/Server/game/src/game.vcxproj b/s3ll_server/Srcs/Server/game/src/game.vcxproj
index a17be2d4..60c008dc 100644
--- a/s3ll_server/Srcs/Server/game/src/game.vcxproj
+++ b/s3ll_server/Srcs/Server/game/src/game.vcxproj
@@ -258,6 +258,7 @@
<ClCompile Include="questmanager.cpp" />
<ClCompile Include="questnpc.cpp" />
<ClCompile Include="questpc.cpp" />
+ <ClCompile Include="QuizShow.cpp" />
<ClCompile Include="refine.cpp" />
<ClCompile Include="regen.cpp" />
<ClCompile Include="safebox.cpp" />
@@ -383,6 +384,7 @@
<ClInclude Include="questmanager.h" />
<ClInclude Include="questnpc.h" />
<ClInclude Include="questpc.h" />
+ <ClInclude Include="QuizShow.h" />
<ClInclude Include="refine.h" />
<ClInclude Include="regen.h" />
<ClInclude Include="safebox.h" />
diff --git a/s3ll_server/Srcs/Server/game/src/game.vcxproj.filters b/s3ll_server/Srcs/Server/game/src/game.vcxproj.filters
index d31f5a72..b7cb67bc 100644
--- a/s3ll_server/Srcs/Server/game/src/game.vcxproj.filters
+++ b/s3ll_server/Srcs/Server/game/src/game.vcxproj.filters
@@ -472,6 +472,9 @@
<ClCompile Include="JobManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="QuizShow.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="affect.h">
@@ -856,6 +859,9 @@
<ClInclude Include="JobManager.h">
<Filter>Source Files</Filter>
</ClInclude>
+ <ClInclude Include="QuizShow.h">
+ <Filter>Source Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="any_function.inc">
diff --git a/s3ll_server/Srcs/Server/game/src/input_main.cpp b/s3ll_server/Srcs/Server/game/src/input_main.cpp
index 70d6b193..00e53702 100644
--- a/s3ll_server/Srcs/Server/game/src/input_main.cpp
+++ b/s3ll_server/Srcs/Server/game/src/input_main.cpp
@@ -21,6 +21,8 @@
#include "questmanager.h"
#include "profiler.h"
#include "messenger_manager.h"
+#include "QuizShow.h"
+#include <fmt/format.h>
#include "party.h"
#include "p2p.h"
#include "affect.h"
@@ -831,6 +833,22 @@ int CInputMain::Chat(LPCHARACTER ch, const char * data, size_t uiBytes)
}
}
break;
+
+ case CHAT_TYPE_QUIZ:
+ {
+ if (!CQuizShowManager::instance().IsRunning())
+ {
+ ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("Quiz event is not running."));
+ }
+ else
+ {
+ // Broadcast answer to watchers before processing
+ std::string watcherMessage = fmt::format("{}: {}", ch->GetName(), buf);
+ CQuizShowManager::instance().BroadcastToWatchers(watcherMessage);
+
+ CQuizShowManager::instance().HandleAnswer(ch, std::string(buf, buflen));
+ }
+ } break;
case CHAT_TYPE_GUILD:
{
diff --git a/s3ll_server/Srcs/Server/game/src/main.cpp b/s3ll_server/Srcs/Server/game/src/main.cpp
index d650d4a9..a167bf9f 100644
--- a/s3ll_server/Srcs/Server/game/src/main.cpp
+++ b/s3ll_server/Srcs/Server/game/src/main.cpp
@@ -30,6 +30,7 @@
#include "refine.h"
#include "banword.h"
#include "priv_manager.h"
+#include "QuizShow.h"
#include "war_map.h"
#include "building.h"
#include "login_sim.h"
@@ -387,6 +388,7 @@ int main(int argc, char **argv)
CHorseNameManager horsename_manager;
DESC_MANAGER desc_manager;
+ CQuizShowManager quiz_show_manager;
CTableBySkill SkillPowerByLevel;
CPolymorphUtils polymorph_utils;
diff --git a/s3ll_svfiles/main/srv1/share/locale/germany/quizshow.lua b/s3ll_svfiles/main/srv1/share/locale/germany/quizshow.lua
new file mode 100644
index 00000000..98e02404
--- /dev/null
+++ b/s3ll_svfiles/main/srv1/share/locale/germany/quizshow.lua
@@ -0,0 +1,57 @@
+-- Quiz Show Configuration File
+-- This file contains questions and rewards for the quiz show system
+
+-- Questions table - each question has a question text (q) and array of answers (a)
+quiz_questions = {
+ {
+ q = "What is the capital of France?",
+ a = {"Paris"}
+ },
+ {
+ q = "What are the primary colors?",
+ a = {"Red", "Blue", "Yellow"}
+ },
+ {
+ q = "Name planets in our solar system",
+ a = {"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"}
+ },
+ {
+ q = "What are common web browsers?",
+ a = {"Chrome", "Firefox", "Safari", "Edge", "Opera"}
+ }
+}
+
+-- Rewards table - items given to the winner
+-- Each reward has: vnum (item ID), count (quantity), name (display name - optional)
+--
+-- HARDCODED SPECIAL IDs:
+-- vnum = 1: Direct Yang/Gold (count = amount of yang to give)
+-- vnum = 2: Direct Experience Points (count = amount of exp to give)
+-- All other vnums are regular item IDs from your item_proto
+quiz_rewards = {
+ {
+ vnum = 1, -- HARDCODED: Direct Yang/Gold
+ count = 1000000,
+ name = "Yang"
+ },
+ {
+ vnum = 2, -- HARDCODED: Direct Experience Points
+ count = 500000,
+ name = "Experience"
+ },
+ {
+ vnum = 50300, -- Regular item: Skill Book
+ count = 10,
+ name = "Skill Book"
+ },
+ {
+ vnum = 70038, -- Regular item: Bravery Cape
+ count = 1,
+ name = "Bravery Cape"
+ },
+ {
+ vnum = 50001, -- Regular item: Lucky Book
+ count = 5,
+ name = "Lucky Book"
+ }
+}
\ No newline at end of file
--
2.47.1.windows.2
© 2006 - 2025 Paste2.org.
Follow paste2.org on Twitter
locale kısmı eksik kalmış, locale/../locale_game.txt dosyasına ekleyin:
Kod:
CHAT_QUIZ Etkinlik
Sublime text ya da benzeri modern bir IDE ile ya da online olarak
Linkleri görebilmek için
giriş yap veya kayıt ol.
ya da benzeri bir site ile daha düzgün görüntüleyebilirsiniz.Event komutları:
/quiz_manage <start/end/scores>
En son bir moderatör tarafından düzenlenmiş: