Skocz do zawartości
Masz już aplikację Sharegon?

Odkryj wszystkie możliwości. Dowiedz się więcej

Sharegon.pl

Zainstaluj aplikację Sharegon i korzystaj z powiadomień push oraz licznika nowych aktywności bezpośrednio z ekranu głównego.

Aby zainstalować tę aplikację na iOS i iPadOS.
  1. Tap the Share icon in Safari
  2. Przewiń menu i stuknij Dodaj do ekranu początkowego.
  3. Stuknij Dodaj w prawym górnym rogu.
Zainstaluj aplikację Sharegon na Androidzie
  1. Otwórz Sklep Google Play na swoim smarfonie.
  2. Wyszukaj „Sharegon” w pasku wyszukiwania.
  3. Stuknij „Zainstaluj”, aby pobrać aplikację.

Jak prawidłowo obsługiwać dodawanie przedmiotów przypisywanych do itemshop przez API (DB vs TCP/IP) – co zrobić w tej sytuacji?

Nieaktywny

Featured Replies

Opublikowano

Witam, mam pytanie odnośnie "API" do metina, zrobiłem sobie stronę gdzie obsługiwana jest rejestracja, realizowanie kodów, ilość osób online itp.

Teraz już widzę ze sposób sprawdzania ilości osób online po kolumnie "last_play" to patologia i są specjalne funkcje w pliku input.cpp do tego, ale tu pojawia się moje pytanie

jak np. powinno być realizowane dodawanie przedmiotu do magazynu itemshop z kodu który jest wygenerowany jako kod promocyjny (np. FB_PIERSCIEN), powinienem zrobić sobie tam funkcje, która obsłuży mi to czy mogę walić bezpośrednio przez bazę?

I czy większość powinienem robić przez TCPIP, czy zabawa z bazą będzie ok (obsłużony kesz).

Dołączam funkcje o której napisałem i czy ją powinienem rozwijać.

int CInputHandshake::Analyze(LPDESC d, BYTE bHeader, const char * c_pData)
{
	if (bHeader == 10)
		return 0;

	if (bHeader == HEADER_CG_TEXT)
	{
#ifdef ENABLE_PORT_SECURITY
		if (IsEmptyAdminPage() || !IsAdminPage(inet_ntoa(d->GetAddr().sin_addr))) // block if adminpage is not set or if not admin
		{
			sys_log(0, "SOCKET_CMD: BLOCK FROM(%s)", d->GetHostName());
			d->SetPhase(PHASE_CLOSE);
			return 0;
		}
#endif
		++c_pData;
		const char * c_pSep;

		if (!(c_pSep = strchr(c_pData, '\n')))
		{
			d->SetPhase(PHASE_CLOSE); // @fixme187 setphase+ret0
			return 0;
		}

		if (*(c_pSep - 1) == '\r')
			--c_pSep;

		std::string stResult;
		std::string stBuf;
		stBuf.assign(c_pData, 0, c_pSep - c_pData);

		sys_log(0, "SOCKET_CMD: FROM(%s) CMD(%s)", d->GetHostName(), stBuf.c_str());

		if (!stBuf.compare("IS_SERVER_UP"))
		{
			if (g_bNoMoreClient)
				stResult = "NO";
			else
				stResult = "YES";
		}
		else if (stBuf == g_stAdminPagePassword)
		{
			if (!IsEmptyAdminPage())
			{
				if (!IsAdminPage(inet_ntoa(d->GetAddr().sin_addr)))
				{
					char szTmp[64];
					snprintf(szTmp, sizeof(szTmp), "WEBADMIN : Wrong Connector : %s", inet_ntoa(d->GetAddr().sin_addr));
					stResult += szTmp;
				}
				else
				{
					d->SetAdminMode();
					stResult = "UNKNOWN";
				}
			}
			else
			{
				d->SetAdminMode();
				stResult = "UNKNOWN";
			}
		}
		else if (!stBuf.compare("USER_COUNT"))
		{
			char szTmp[64];

			if (!IsEmptyAdminPage())
			{
				if (!IsAdminPage(inet_ntoa(d->GetAddr().sin_addr)))
				{
					snprintf(szTmp, sizeof(szTmp), "WEBADMIN : Wrong Connector : %s", inet_ntoa(d->GetAddr().sin_addr));
				}
				else
				{
					int iTotal;
					int * paiEmpireUserCount;
					int iLocal;
					DESC_MANAGER::instance().GetUserCount(iTotal, &paiEmpireUserCount, iLocal);
					snprintf(szTmp, sizeof(szTmp), "%d %d %d %d %d", iTotal, paiEmpireUserCount[1], paiEmpireUserCount[2], paiEmpireUserCount[3], iLocal);
				}
			}
			else
			{
				int iTotal;
				int * paiEmpireUserCount;
				int iLocal;
				DESC_MANAGER::instance().GetUserCount(iTotal, &paiEmpireUserCount, iLocal);
				snprintf(szTmp, sizeof(szTmp), "%d %d %d %d %d", iTotal, paiEmpireUserCount[1], paiEmpireUserCount[2], paiEmpireUserCount[3], iLocal);
			}
			stResult += szTmp;
		}
		else if (!stBuf.compare("CHECK_P2P_CONNECTIONS"))
		{
			std::ostringstream oss(std::ostringstream::out);

			oss << "P2P CONNECTION NUMBER : " << P2P_MANAGER::instance().GetDescCount() << "\n";
			std::string hostNames;
			P2P_MANAGER::Instance().GetP2PHostNames(hostNames);
			oss << hostNames;
			stResult = oss.str();
			TPacketGGCheckAwakeness packet;
			packet.bHeader = HEADER_GG_CHECK_AWAKENESS;

			P2P_MANAGER::instance().Send(&packet, sizeof(packet));
		}
		else if (!stBuf.compare("PACKET_INFO"))
		{
			m_pMainPacketInfo->Log("packet_info.txt");
			stResult = "OK";
		}
		else if (!stBuf.compare("PROFILE"))
		{
			CProfiler::instance().Log("profile.txt");
			stResult = "OK";
		}
		//gift notify delete command
		else if (!stBuf.compare(0,15,"DELETE_AWARDID "))
			{
				char szTmp[64];
				std::string msg = stBuf.substr(15,26);

				TPacketDeleteAwardID p;
				p.dwID = (DWORD)(atoi(msg.c_str()));
				snprintf(szTmp,sizeof(szTmp),"Sent to DB cache to delete ItemAward, id: %d",p.dwID);
				//sys_log(0,"%d",p.dwID);
				// strlcpy(p.login, msg.c_str(), sizeof(p.login));
				db_clientdesc->DBPacket(HEADER_GD_DELETE_AWARDID, 0, &p, sizeof(p));
				stResult += szTmp;
			}
		else
		{
			stResult = "UNKNOWN";

			if (d->IsAdminMode())
			{
				if (!stBuf.compare(0, 7, "NOTICE "))
				{
					std::string msg = stBuf.substr(7, 50);
					LogManager::instance().CharLog(0, 0, 0, 1, "NOTICE", msg.c_str(), d->GetHostName());
					BroadcastNotice(msg.c_str());
				}
#ifdef ENABLE_FULL_NOTICE
				else if (!stBuf.compare(0, 11, "BIG_NOTICE "))
				{
					std::string msg = stBuf.substr(11, 50);
					LogManager::instance().CharLog(0, 0, 0, 1, "BIG_NOTICE", msg.c_str(), d->GetHostName());
					BroadcastNotice(msg.c_str(), true);
				}
#endif
				else if (!stBuf.compare("SHUTDOWN"))
				{
					LogManager::instance().CharLog(0, 0, 0, 2, "SHUTDOWN", "", d->GetHostName());
					TPacketGGShutdown p;
					p.bHeader = HEADER_GG_SHUTDOWN;
					P2P_MANAGER::instance().Send(&p, sizeof(TPacketGGShutdown));
					sys_err("Accept shutdown command from %s.", d->GetHostName());
					Shutdown(10);
				}
				else if (!stBuf.compare("SHUTDOWN_ONLY"))
				{
					LogManager::instance().CharLog(0, 0, 0, 2, "SHUTDOWN", "", d->GetHostName());
					sys_err("Accept shutdown only command from %s.", d->GetHostName());
					Shutdown(10);
				}
				else if (!stBuf.compare(0, 3, "DC "))
				{
					std::string msg = stBuf.substr(3, LOGIN_MAX_LEN);

					TPacketGGDisconnect pgg;

					pgg.bHeader = HEADER_GG_DISCONNECT;
					strlcpy(pgg.szLogin, msg.c_str(), sizeof(pgg.szLogin));

					P2P_MANAGER::instance().Send(&pgg, sizeof(TPacketGGDisconnect));
					DESC_MANAGER::instance().DestroyLoginKey(msg);
				}
				else if (!stBuf.compare(0, 10, "RELOAD_CRC"))
				{
					LoadValidCRCList();

					BYTE bHeader = HEADER_GG_RELOAD_CRC_LIST;
					P2P_MANAGER::instance().Send(&bHeader, sizeof(BYTE));
					stResult = "OK";
				}
				else if (!stBuf.compare(0, 20, "CHECK_CLIENT_VERSION"))
				{
					CheckClientVersion();

					BYTE bHeader = HEADER_GG_CHECK_CLIENT_VERSION;
					P2P_MANAGER::instance().Send(&bHeader, sizeof(BYTE));
					stResult = "OK";
				}
				else if (!stBuf.compare(0, 6, "RELOAD"))
				{
					if (stBuf.size() == 6)
					{
						LoadStateUserCount();
						db_clientdesc->DBPacket(HEADER_GD_RELOAD_PROTO, 0, NULL, 0);
					}
					else
					{
						char c = stBuf[7];

						switch (LOWER(c))
						{
							case 'u':
								LoadStateUserCount();
								break;

							case 'p':
								db_clientdesc->DBPacket(HEADER_GD_RELOAD_PROTO, 0, NULL, 0);
								break;

							case 'q':
								quest::CQuestManager::instance().Reload();
								break;

							case 'f':
								fishing::Initialize();
								break;

							case 'a':
								db_clientdesc->DBPacket(HEADER_GD_RELOAD_ADMIN, 0, NULL, 0);
								sys_log(0, "Reloading admin infomation.");
								break;
						}
					}
				}
				else if (!stBuf.compare(0, 6, "EVENT "))
				{
					std::istringstream is(stBuf);
					std::string strEvent, strFlagName;
					long lValue;
					is >> strEvent >> strFlagName >> lValue;

					if (!is.fail())
					{
						sys_log(0, "EXTERNAL EVENT FLAG name %s value %d", strFlagName.c_str(), lValue);
						quest::CQuestManager::instance().RequestSetEventFlag(strFlagName, lValue);
						stResult = "EVENT FLAG CHANGE ";
						stResult += strFlagName;
					}
					else
					{
						stResult = "EVENT FLAG FAIL";
					}
				}
				// BLOCK_CHAT
				else if (!stBuf.compare(0, 11, "BLOCK_CHAT "))
				{
					std::istringstream is(stBuf);
					std::string strBlockChat, strCharName;
					long lDuration;
					is >> strBlockChat >> strCharName >> lDuration;

					if (!is.fail())
					{
						sys_log(0, "EXTERNAL BLOCK_CHAT name %s duration %d", strCharName.c_str(), lDuration);

						do_block_chat(NULL, const_cast<char*>(stBuf.c_str() + 11), 0, 0);

						stResult = "BLOCK_CHAT ";
						stResult += strCharName;
					}
					else
					{
						stResult = "BLOCK_CHAT FAIL";
					}
				}
				// END_OF_BLOCK_CHAT
				else if (!stBuf.compare(0, 12, "PRIV_EMPIRE "))
				{
					int	empire, type, value, duration;
					std::istringstream is(stBuf);
					std::string strPrivEmpire;
					is >> strPrivEmpire >> empire >> type >> value >> duration;

					value = MINMAX(0, value, 1000);
					stResult = "PRIV_EMPIRE FAIL";

					if (!is.fail())
					{
						// check parameter
						if (empire < 0 || 3 < empire);
						else if (type < 1 || 4 < type);
						else if (value < 0);
						else if (duration < 0);
						else
						{
							stResult = "PRIV_EMPIRE SUCCEED";

							duration = duration * (60 * 60);

							sys_log(0, "_give_empire_privileage(empire=%d, type=%d, value=%d, duration=%d) by web",
									empire, type, value, duration);
							CPrivManager::instance().RequestGiveEmpirePriv(empire, type, value, duration);
						}
					}
				}
			}
		}

		sys_log(1, "TEXT %s RESULT %s", stBuf.c_str(), stResult.c_str());
		stResult += "\n";
		d->Packet(stResult.c_str(), stResult.length());
		return (c_pSep - c_pData) + 1;
	}
	else if (bHeader == HEADER_CG_MARK_LOGIN)
	{
		if (!guild_mark_server)
		{
			sys_err("Guild Mark login requested but i'm not a mark server!");
			d->SetPhase(PHASE_CLOSE);
			return 0;
		}

		sys_log(0, "MARK_SERVER: Login");
		d->SetPhase(PHASE_LOGIN);
		return 0;
	}
	else if (bHeader == HEADER_CG_STATE_CHECKER)
	{
		if (d->isChannelStatusRequested()) {
			return 0;
		}
		d->SetChannelStatusRequested(true);
		db_clientdesc->DBPacket(HEADER_GD_REQUEST_CHANNELSTATUS, d->GetHandle(), NULL, 0);
	}
	else if (bHeader == HEADER_CG_PONG)
		Pong(d);
	else if (bHeader == HEADER_CG_HANDSHAKE)
		Handshake(d, c_pData);
#ifdef _IMPROVED_PACKET_ENCRYPTION_
	else if (bHeader == HEADER_CG_KEY_AGREEMENT)
	{
		// Send out the key agreement completion packet first
		// to help client to enter encryption mode
		d->SendKeyAgreementCompleted();
		// Flush socket output before going encrypted
		d->ProcessOutput();

		TPacketKeyAgreement* p = (TPacketKeyAgreement*)c_pData;
		if (!d->IsCipherPrepared())
		{
			sys_err ("Cipher isn't prepared. %s maybe a Hacker.", inet_ntoa(d->GetAddr().sin_addr));
			d->DelayedDisconnect(5);
			return 0;
		}
		if (d->FinishHandshake(p->wAgreedLength, p->data, p->wDataLength)) {
			// Handshaking succeeded
			if (g_bAuthServer) {
				d->SetPhase(PHASE_AUTH);
			} else {
				d->SetPhase(PHASE_LOGIN);
			}
		} else {
			sys_log(0, "[CInputHandshake] Key agreement failed: al=%u dl=%u",
				p->wAgreedLength, p->wDataLength);
			d->SetPhase(PHASE_CLOSE);
		}
	}
#endif // _IMPROVED_PACKET_ENCRYPTION_
	else
		sys_err("Handshake phase does not handle packet %d (fd %d) from %s:%u", bHeader, d->GetSocket(), d->GetHostName(), d->GetPort()); //@warme016 host and port

	return 0;
}

Rozwiązane przez wojciech74

Przejdź do rozwiązania
Opublikowano

Zastanów się najpierw czy nie chcesz mieć isa w grze, nie tylko ze względu dodatkowego softu do utrzymania na stronie ale też że gracz będzie miał do niego "bliżej" :)

Opublikowano
  • Autor

Panowie, chodzi o kod typu FB_PIERSCIEN, który mi przypisze przedmiot do magazynu itemshop, a itemshop mam wbudowany w grze "in-game".

Opublikowano
  • Autor
3 minuty temu, samir napisał(a):

To może też obsługę kodów w grze?

ale ja pytam ogólnie, jakie operacje powinny być wykonywane na bazie, a jakie po tcpip - co Twoja "propozycja rozwiązania" wnosi?

Wytłumacz mi kolego jak sprawdzasz ilość graczy online na serwerze, jestem ciekawy odpowiedzi.

Opublikowano

Możesz czytać listę aktywnych połączeń aktualnie albo napisać coś swojego co będzie sumować po p2p liczbę np m_map_pkPCChr w tym "api" o którym mówisz. Co z tymi danymi zrobisz jak je przechowasz i potem odczytasz to już od Ciebie zależy. Ja Ci po prostu proponuje bardziej przystępne rozwiązanie żebyś nie musiał się kopać :)

Opublikowano
  • Autor
7 minut temu, samir napisał(a):

Możesz czytać listę aktywnych połączeń aktualnie albo napisać coś swojego co będzie sumować po p2p liczbę np m_map_pkPCChr w tym "api" o którym mówisz. Co z tymi danymi zrobisz jak je przechowasz i potem odczytasz to już od Ciebie zależy. Ja Ci po prostu proponuje bardziej przystępne rozwiązanie żebyś nie musiał się kopać :)

		else if (!stBuf.compare("CHECK_P2P_CONNECTIONS"))
		{
			std::ostringstream oss(std::ostringstream::out);

			oss << "P2P CONNECTION NUMBER : " << P2P_MANAGER::instance().GetDescCount() << "\n";
			std::string hostNames;
			P2P_MANAGER::Instance().GetP2PHostNames(hostNames);
			oss << hostNames;
			stResult = oss.str();
			TPacketGGCheckAwakeness packet;
			packet.bHeader = HEADER_GG_CHECK_AWAKENESS;

			P2P_MANAGER::instance().Send(&packet, sizeof(packet));
		} 


hmm czyli o tym piszesz, ciekawe.

Opublikowano

Coś na tej zasadzie, sprawdzanie po tym last_play to też nie takie głupie tylko zadbać o to żeby może częściej zostało aktualizowane. Pytanie jaki problem rozwiązujesz tym, że chcesz mieć liczbę online co do sekund a nie minut? Jednak co do samego wątku już się wypowiedziałem, możesz bawić się w jakiś nowy potencjalnie awaryjny soft, który będziesz musiał napisać, przetestować i zmusić gracza do wejścia na stronę po to żeby sobie "coś" odebrać. Tak to wszystko robisz w grze, gracz ma to od razu pod ręką, korzystasz z gotowych metod które są już w kodzie a dodatkowo piszesz to w mniej niż 30 minut.

Opublikowano
  • Rozwiązanie

Panie do takich rzeczy to awards_item czy jakoś tak, masz gotowy kod do tego w src, sam mam tak zrobione dla kodów promocyjnych.

Opublikowano
  • Autor
21 godzin temu, wojciech74 napisał(a):

Panie do takich rzeczy to awards_item czy jakoś tak, masz gotowy kod do tego w src, sam mam tak zrobione dla kodów promocyjnych.

image.png

Jakby kogoś to interesowało, to po prostu na swoim backendzie uzupełniacie tą tabelę item_award np. na podstawie takiego jsona.

{
  "rewards": {
    "10": {
      "name": "Kosmiczny Pakiet",
      "items": [
        {
          "type": "item",
          "vnum": 11234,
          "count": 1,
          "socket0": 9999,
          "socket1": 123,
          "socket2": 44,
          "attrs": [
            { "type": 1, "value": 15 },
            { "type": 7, "value": 10 }
          ]
        },
        {
          "type": "item",
          "vnum": 80001,
          "count": 3,
          "socket1": 5
        }
      ]
    }
  }
}

I ItemAwardManager.cpp i ClientManager.cpp ładnie wam tym będzie zarządzać nie musicie się martwić miejscem itp.
Pozdrawiam

Opublikowano

Jakby ktoś chciał to zgrać z kodami to mniej więcej tak, trzeba dopisać obsługę attrtype/value.

redeem_promo.php

<?php
/**
 * API Endpoint - Redeem Promo Code
 * Handles promo code redemption for users
 */
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/config.php';
// Initialize language system
Language::init();
// Check if user is logged in
if (!isLoggedIn()) {
    http_response_code(401);
    echo json_encode([
        'success' => false,
        'message' => 'You must be logged in to redeem codes'
    ]);
    exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode([
        'success' => false,
        'message' => 'Method not allowed'
    ]);
    exit;
}
// CSRF Protection
requireCsrfToken();
try {
    $code = strtoupper(trim($_POST['code'] ?? ''));
    if (empty($code)) {
        throw new Exception(t('promo_code_invalid', 'Please enter a promo code'));
    }
    $db = Database::getInstance();
    $accountId = $_SESSION['user_id'];
    $accountLogin = $_SESSION['username'];
    // Get promo code details
    $stmt = $db->query("
        SELECT * FROM srv1_common.promo_codes
        WHERE code = :code
        LIMIT 1
    ", ['code' => $code]);
    $promoCode = $stmt ? $stmt->fetch(PDO::FETCH_ASSOC) : null;
    if (!$promoCode) {
        throw new Exception(t('promo_code_invalid', 'Invalid promo code'));
    }
    // Check if code is active
    if (!$promoCode['is_active']) {
        throw new Exception(t('promo_code_inactive', 'This promo code is no longer active'));
    }
    // Check if code has started
    if (strtotime($promoCode['start_date']) > time()) {
        throw new Exception(t('promo_code_invalid', 'This promo code is not yet available'));
    }
    // Check if code has expired
    if ($promoCode['end_date'] && strtotime($promoCode['end_date']) < time()) {
        throw new Exception(t('promo_code_expired', 'This promo code has expired'));
    }
    // Check total usage limit
    if ($promoCode['max_uses']) {
        $stmt = $db->query("
            SELECT COUNT(*) as count
            FROM srv1_common.promo_code_redemptions
            WHERE promo_code_id = :id
        ", ['id' => $promoCode['id']]);
        $usage = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($usage['count'] >= $promoCode['max_uses']) {
            throw new Exception(t('promo_code_max_uses', 'This promo code has reached its maximum usage limit'));
        }
    }
    // Check per-account usage limit
    $stmt = $db->query("
        SELECT COUNT(*) as count
        FROM srv1_common.promo_code_redemptions
        WHERE promo_code_id = :id AND account_id = :account_id
    ", [
        'id' => $promoCode['id'],
        'account_id' => $accountId
    ]);
    $accountUsage = $stmt->fetch(PDO::FETCH_ASSOC);
    if ($accountUsage['count'] >= $promoCode['uses_per_account']) {
        throw new Exception(t('promo_code_already_used', 'You have already used this promo code the maximum number of times'));
    }
    // Get rewards
    $stmt = $db->query("
        SELECT * FROM srv1_common.promo_code_rewards
        WHERE promo_code_id = :id
    ", ['id' => $promoCode['id']]);
    $rewards = $stmt ? $stmt->fetchAll(PDO::FETCH_ASSOC) : [];
    if (empty($rewards)) {
        throw new Exception(t('promo_code_invalid', 'This promo code has no rewards configured'));
    }
    // Begin transaction
    $db->getConnection()->beginTransaction();
    // Record redemption
    $stmt = $db->getConnection()->prepare("
        INSERT INTO srv1_common.promo_code_redemptions (
            promo_code_id, account_id, account_login
        ) VALUES (
            :promo_code_id, :account_id, :account_login
        )
    ");
    $stmt->execute([
        'promo_code_id' => $promoCode['id'],
        'account_id' => $accountId,
        'account_login' => $accountLogin
    ]);
    // Process rewards
    $rewardMessages = [];
    foreach ($rewards as $reward) {
        switch ($reward['reward_type']) {
            case 'item':
                // Insert into item_award table (in-game mailbox)
                $itemStmt = $db->getConnection()->prepare("
                    INSERT INTO srv1_player.item_award (
                        login, vnum, count, socket0, socket1, socket2, why, mall
                    ) VALUES (
                        :login, :vnum, :count, :socket0, :socket1, :socket2, :why, 1
                    )
                ");
                $itemStmt->execute([
                    'login' => $accountLogin,
                    'vnum' => $reward['item_vnum'],
                    'count' => $reward['item_count'],
                    'socket0' => $reward['socket0'],
                    'socket1' => $reward['socket1'],
                    'socket2' => $reward['socket2'],
                    'why' => 'Promo Code: ' . $code
                ]);
                $rewardMessages[] = 'Item (Vnum ' . $reward['item_vnum'] . ') x' . $reward['item_count'] . ' sent to mailbox';
                break;
            case 'coins':
                // Add coins to account
                $coinStmt = $db->getConnection()->prepare("
                    UPDATE srv1_account.account
                    SET coins = coins + :amount
                    WHERE id = :account_id
                ");
                $coinStmt->execute([
                    'amount' => $reward['coins_amount'],
                    'account_id' => $accountId
                ]);
                $_SESSION['coins'] = ($_SESSION['coins'] ?? 0) + $reward['coins_amount'];
                $rewardMessages[] = number_format($reward['coins_amount']) . ' Coins added';
                break;
            case 'cash':
                // Add cash (SM) to account
                $cashStmt = $db->getConnection()->prepare("
                    UPDATE srv1_account.account
                    SET cash = cash + :amount
                    WHERE id = :account_id
                ");
                $cashStmt->execute([
                    'amount' => $reward['cash_amount'],
                    'account_id' => $accountId
                ]);
                $_SESSION['cash'] = ($_SESSION['cash'] ?? 0) + $reward['cash_amount'];
                $rewardMessages[] = number_format($reward['cash_amount']) . ' SM (Cash) added';
                break;
        }
    }
    // Commit transaction
    $db->getConnection()->commit();
    echo json_encode([
        'success' => true,
        'message' => t('promo_code_success', 'Promo code redeemed successfully!'),
        'rewards' => $rewardMessages
    ]);
} catch (Exception $e) {
    if (isset($db) && $db->getConnection()->inTransaction()) {
        $db->getConnection()->rollBack();
    }
    echo json_encode([
        'success' => false,
        'message' => $e->getMessage()
    ]);
}
?>

promo_codes.php

<?php
/**
 * API Endpoint - Promo Codes Management
 */
require_once dirname(dirname(dirname(__FILE__))) . '/config.php';
// Check admin access
if (!isLoggedIn() || !isAdmin()) {
    http_response_code(403);
    echo json_encode(['success' => false, 'message' => 'Unauthorized']);
    exit;
}
header('Content-Type: application/json');
$action = $_GET['action'] ?? '';
try {
    $db = Database::getInstance();
    switch ($action) {
        case 'create':
            handleCreate($db);
            break;
        case 'toggle_status':
            handleToggleStatus($db);
            break;
        case 'delete':
            handleDelete($db);
            break;
        default:
            throw new Exception('Invalid action');
    }
} catch (Exception $e) {
    http_response_code(500);
    echo json_encode([
        'success' => false,
        'message' => $e->getMessage()
    ]);
}
/**
 * Create new promo code
 */
function handleCreate($db) {
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        throw new Exception('Method not allowed');
    }
    // Validate required fields
    $code = strtoupper(trim($_POST['code'] ?? ''));
    $description = trim($_POST['description'] ?? '');
    $maxUses = !empty($_POST['max_uses']) ? (int)$_POST['max_uses'] : null;
    $usesPerAccount = (int)($_POST['uses_per_account'] ?? 1);
    $startDate = !empty($_POST['start_date']) ? $_POST['start_date'] : date('Y-m-d H:i:s');
    $endDate = !empty($_POST['end_date']) ? $_POST['end_date'] : null;
    $rewards = $_POST['rewards'] ?? [];
    if (empty($code) || empty($description)) {
        throw new Exception('Code and description are required');
    }
    if (!preg_match('/^[A-Z0-9]+$/', $code)) {
        throw new Exception('Code can only contain uppercase letters and numbers');
    }
    if (empty($rewards)) {
        throw new Exception('At least one reward is required');
    }
    // Check if code already exists
    $stmt = $db->query(
        "SELECT id FROM srv1_common.promo_codes WHERE code = :code LIMIT 1",
        ['code' => $code]
    );
    if ($stmt && $stmt->fetch()) {
        throw new Exception('This promo code already exists');
    }
    // Insert promo code
    $stmt = $db->getConnection()->prepare("
        INSERT INTO srv1_common.promo_codes (
            code, description, max_uses, uses_per_account,
            start_date, end_date, is_active, created_by
        ) VALUES (
            :code, :description, :max_uses, :uses_per_account,
            :start_date, :end_date, 1, :created_by
        )
    ");
    $stmt->execute([
        'code' => $code,
        'description' => $description,
        'max_uses' => $maxUses,
        'uses_per_account' => $usesPerAccount,
        'start_date' => $startDate,
        'end_date' => $endDate,
        'created_by' => $_SESSION['username']
    ]);
    $promoCodeId = $db->getConnection()->lastInsertId();
    // Insert rewards
    $rewardStmt = $db->getConnection()->prepare("
        INSERT INTO srv1_common.promo_code_rewards (
            promo_code_id, reward_type, item_vnum, item_count,
            coins_amount, cash_amount, socket0, socket1, socket2
        ) VALUES (
            :promo_code_id, :reward_type, :item_vnum, :item_count,
            :coins_amount, :cash_amount, :socket0, :socket1, :socket2
        )
    ");
    foreach ($rewards as $reward) {
        if (empty($reward['type'])) continue;
        $rewardStmt->execute([
            'promo_code_id' => $promoCodeId,
            'reward_type' => $reward['type'],
            'item_vnum' => $reward['item_vnum'] ?? null,
            'item_count' => $reward['item_count'] ?? 1,
            'coins_amount' => $reward['coins_amount'] ?? null,
            'cash_amount' => $reward['cash_amount'] ?? null,
            'socket0' => $reward['socket0'] ?? 0,
            'socket1' => $reward['socket1'] ?? 0,
            'socket2' => $reward['socket2'] ?? 0
        ]);
    }
    echo json_encode([
        'success' => true,
        'message' => 'Promo code created successfully',
        'promo_code_id' => $promoCodeId
    ]);
}
/**
 * Toggle promo code status
 */
function handleToggleStatus($db) {
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        throw new Exception('Method not allowed');
    }
    $id = (int)($_POST['id'] ?? 0);
    $status = (int)($_POST['status'] ?? 0);
    if ($id <= 0) {
        throw new Exception('Invalid promo code ID');
    }
    $stmt = $db->getConnection()->prepare("
        UPDATE srv1_common.promo_codes
        SET is_active = :status
        WHERE id = :id
    ");
    $stmt->execute([
        'id' => $id,
        'status' => $status
    ]);
    echo json_encode([
        'success' => true,
        'message' => 'Promo code status updated'
    ]);
}
/**
 * Delete promo code
 */
function handleDelete($db) {
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        throw new Exception('Method not allowed');
    }
    $id = (int)($_POST['id'] ?? 0);
    if ($id <= 0) {
        throw new Exception('Invalid promo code ID');
    }
    $stmt = $db->getConnection()->prepare("
        DELETE FROM srv1_common.promo_codes WHERE id = :id
    ");
    $stmt->execute(['id' => $id]);
    echo json_encode([
        'success' => true,
        'message' => 'Promo code deleted successfully'
    ]);
}
?>

SQL

/*
 Navicat Premium Data Transfer
 Source Server         : VPS
 Source Server Type    : MariaDB
 Source Server Version : 101114 (10.11.14-MariaDB-log)
 Source Host           :
 Source Schema         : srv1_common
 Target Server Type    : MariaDB
 Target Server Version : 101114 (10.11.14-MariaDB-log)
 File Encoding         : 65001
 Date: 10/12/2025 19:45:30
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for promo_code_redemptions
-- ----------------------------
DROP TABLE IF EXISTS promo_code_redemptions;
CREATE TABLE promo_code_redemptions  (
  id int(11) NOT NULL AUTO_INCREMENT,
  promo_code_id int(11) NOT NULL,
  account_id int(10) UNSIGNED NOT NULL,
  account_login varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  redeemed_at datetime NULL DEFAULT current_timestamp(),
  PRIMARY KEY id) USING BTREE,
  INDEX idx_accountaccount_id) USING BTREE,
  INDEX idx_loginaccount_login) USING BTREE,
  INDEX idx_code_accountpromo_code_id, account_id) USING BTREE,
  INDEX idx_redeemed_atredeemed_at) USING BTREE,
  CONSTRAINT promo_code_redemptions_ibfk_1 FOREIGN KEY promo_code_id) REFERENCES promo_codes id) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for promo_code_rewards
-- ----------------------------
DROP TABLE IF EXISTS promo_code_rewards;
CREATE TABLE promo_code_rewards  (
  id int(11) NOT NULL AUTO_INCREMENT,
  promo_code_id int(11) NOT NULL,
  reward_type enum('item','coins','cash') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  item_vnum int(11) NULL DEFAULT NULL COMMENT 'For items - item vnum from item_proto',
  item_count int(11) NULL DEFAULT 1,
  coins_amount int(11) NULL DEFAULT NULL COMMENT 'For coins reward',
  cash_amount int(11) NULL DEFAULT NULL COMMENT 'For SM/cash reward',
  socket0 int(11) NULL DEFAULT 0,
  socket1 int(11) NULL DEFAULT 0,
  socket2 int(11) NULL DEFAULT 0,
  attrtype0 tinyint(3) UNSIGNED NULL DEFAULT 0,
  attrvalue0 smallint(6) NULL DEFAULT 0,
  attrtype1 tinyint(3) UNSIGNED NULL DEFAULT 0,
  attrvalue1 smallint(6) NULL DEFAULT 0,
  attrtype2 tinyint(3) UNSIGNED NULL DEFAULT 0,
  attrvalue2 smallint(6) NULL DEFAULT 0,
  attrtype3 tinyint(3) UNSIGNED NULL DEFAULT 0,
  attrvalue3 smallint(6) NULL DEFAULT 0,
  attrtype4 tinyint(3) UNSIGNED NULL DEFAULT 0,
  attrvalue4 smallint(6) NULL DEFAULT 0,
  attrtype5 tinyint(3) UNSIGNED NULL DEFAULT 0,
  attrvalue5 smallint(6) NULL DEFAULT 0,
  attrtype6 tinyint(3) UNSIGNED NULL DEFAULT 0,
  attrvalue6 smallint(6) NULL DEFAULT 0,
  PRIMARY KEY id) USING BTREE,
  INDEX idx_promo_codepromo_code_id) USING BTREE,
  CONSTRAINT promo_code_rewards_ibfk_1 FOREIGN KEY promo_code_id) REFERENCES promo_codes id) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for promo_codes
-- ----------------------------
DROP TABLE IF EXISTS promo_codes;
CREATE TABLE promo_codes  (
  id int(11) NOT NULL AUTO_INCREMENT,
  code varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  description varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  max_uses int(11) NULL DEFAULT NULL COMMENT 'NULL = unlimited',
  uses_per_account int(11) NULL DEFAULT 1 COMMENT 'How many times one account can use',
  start_date datetime NULL DEFAULT current_timestamp(),
  end_date datetime NULL DEFAULT NULL COMMENT 'NULL = no expiry',
  is_active tinyint(1) NULL DEFAULT 1,
  created_by varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  created_at datetime NULL DEFAULT current_timestamp(),
  PRIMARY KEY id) USING BTREE,
  UNIQUE INDEX codecode) USING BTREE,
  INDEX idx_codecode) USING BTREE,
  INDEX idx_activeis_active) USING BTREE,
  INDEX idx_datesstart_date, end_date) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
Gość
Ten temat został zamknięty. Brak możliwości dodania odpowiedzi.

Konto

Nawigacja

Skonfiguruj powiadomienia push w przeglądarce.

Chrome (Android)
  1. Stuknij ikonę kłódki obok paska adresu.
  2. Wybierz Uprawnienia → Powiadomienia.
  3. Dostosuj swoje preferencje.
Chrome (Desktop)
  1. Kliknij ikonę kłódki na pasku adresu.
  2. Wybierz Ustawienia witryny.
  3. Znajdź Powiadomienia i dostosuj swoje preferencje.