You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
526 lines
15 KiB
HTML
526 lines
15 KiB
HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
|
<html lang="en">
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
|
|
<title>AI RPG</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
<style type="text/css">
|
|
body {
|
|
margin: 0;
|
|
background-color: black;
|
|
}
|
|
.game {
|
|
position: absolute;
|
|
top: 0px;
|
|
left: 0px;
|
|
margin: 0px;
|
|
border: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
display: block;
|
|
image-rendering: optimizeSpeed;
|
|
image-rendering: -moz-crisp-edges;
|
|
image-rendering: -o-crisp-edges;
|
|
image-rendering: -webkit-optimize-contrast;
|
|
image-rendering: optimize-contrast;
|
|
image-rendering: crisp-edges;
|
|
image-rendering: pixelated;
|
|
-ms-interpolation-mode: nearest-neighbor;
|
|
}
|
|
#inputdiv, #buy_menu {
|
|
position: absolute;
|
|
top: 0px;
|
|
left: 0px;
|
|
margin: 0px;
|
|
border: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
#inputdiv {
|
|
z-index: 10;
|
|
display: none; /* set to flex by javascript */
|
|
overflow: hidden;
|
|
background: #00000090;
|
|
}
|
|
#inputtext {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
|
|
background: none;
|
|
width: 90%;
|
|
height: 50%;
|
|
margin-left: 5%;
|
|
margin-right: 5%;
|
|
border-top-style: hidden;
|
|
border-right-style: hidden;
|
|
border-left-style: hidden;
|
|
border-bottom-style: hidden;
|
|
transform: translateY(100%);
|
|
text-align: center;
|
|
font-size: 2em;
|
|
font-family: Arial;
|
|
color: white;
|
|
}
|
|
|
|
#inputtext:focus {
|
|
outline: none;
|
|
}
|
|
|
|
#inputbuttondiv {
|
|
bottom: 0;
|
|
margin-bottom: 10%;
|
|
position: absolute;
|
|
}
|
|
|
|
#inputbutton {
|
|
font-family: Arial;
|
|
font-size: 3em;
|
|
}
|
|
|
|
#buy_menu {
|
|
display: none; /* set in javascript when shown when error code */
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
word-wrap: break-word;
|
|
align-items: center;
|
|
text-align: center;
|
|
height: 100vh;
|
|
background: #000000D0;
|
|
font-family: arial;
|
|
gap: 20px;
|
|
color: #fff;
|
|
z-index: 20;
|
|
}
|
|
|
|
#buy_menu h1, #daypass {
|
|
margin-left: 30px;
|
|
margin-right: 30px;
|
|
}
|
|
|
|
/* Style the payment screen title */
|
|
#buy_menu h1 {
|
|
font-size: 32px;
|
|
}
|
|
|
|
/* Style the payment button */
|
|
#payment-button, #submit-button {
|
|
background-color: #fff;
|
|
color: #000;
|
|
font-size: 24px;
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
}
|
|
#daypass {
|
|
border: 4px solid white;
|
|
border-radius: 25px;
|
|
padding: 10px;
|
|
display: flex;
|
|
gap: 20px;
|
|
}
|
|
#codebox {
|
|
border: 4px solid white;
|
|
border-radius: 10px;
|
|
padding: 10px;
|
|
margin: 30px;
|
|
}
|
|
#visibility-toggle, #closebutton {
|
|
border: none;
|
|
font-size: 30px;
|
|
background: none;
|
|
color: white;
|
|
}
|
|
#closebutton {
|
|
float: right;
|
|
}
|
|
/* Media queries for smaller screens */
|
|
@media screen and (max-width: 480px) {
|
|
#buy_menu h1 {
|
|
font-size: 24px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
#payment-button {
|
|
font-size: 20px;
|
|
padding: 8px 16px;
|
|
}
|
|
}
|
|
</style>
|
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
|
</head>
|
|
|
|
<body style="background:black">
|
|
<div id="buy_menu">
|
|
<h1>AI is expensive so you need to pay for it</h1>
|
|
<p>You can play free 10 minutes a day. If you're seeing this you must buy a day pass to continue. If you already bought one, but are still seeing this, check that the code matches up. If you're unsatisfied with your service, you can get a refund via the email stripe sent you.</p>
|
|
<div id="daypass">
|
|
<button id="closebutton" onclick="closepayment()"><i class="fa-regular fa-xmark"></i></button>
|
|
<p>24 Hour Pass</p>
|
|
<button id="payment-button" onclick="do_checkout()">Buy Now</button>
|
|
</div>
|
|
<div id="codebox">
|
|
<label for="pass">Day pass code:</label>
|
|
<input type="password" name="pass" id="codeinput" onkeyup='on_codeinput_key(event);' required>
|
|
<button id="visibility-toggle" onclick="visibility_toggle()"><i class="fa fa-eye-slash" id="visibility-toggle-eye"></i></button>
|
|
</div>
|
|
</div>
|
|
<div id="inputdiv" class="inputdiv">
|
|
<textarea onkeyup='on_textarea_key(event);' id="inputtext">
|
|
</textarea>
|
|
<div id="inputbuttondiv">
|
|
<button id="inputbutton" onclick="end_dialog()"> Submit </button>
|
|
<button id="inputbutton" onclick="end_without_saying()"> Cancel </button>
|
|
</div>
|
|
</div>
|
|
<canvas class="game" id="canvas" oncontextmenu="event.preventDefault()" width="1504" height="864"></canvas>
|
|
|
|
<script type="text/javascript">
|
|
var Module = {
|
|
preRun: [],
|
|
postRun: [],
|
|
print: (function() {
|
|
return function(text) {
|
|
text = Array.prototype.slice.call(arguments).join(' ');
|
|
console.log(text);
|
|
};
|
|
})(),
|
|
printErr: function(text) {
|
|
text = Array.prototype.slice.call(arguments).join(' ');
|
|
console.error(text);
|
|
},
|
|
canvas: (function() {
|
|
var canvas = document.getElementById('canvas');
|
|
canvas.addEventListener("webglcontextlost", function(e) { alert('FIXME: WebGL context lost, please reload the page'); e.preventDefault(); }, false);
|
|
return canvas;
|
|
})(),
|
|
setStatus: function(text) { },
|
|
monitorRunDependencies: function(left) { },
|
|
};
|
|
window.onerror = function(event) {
|
|
console.log("onerror: " + event.message);
|
|
};
|
|
</script>
|
|
<script type="text/javascript">
|
|
|
|
let game_doing_input = true;
|
|
let server_url = "";
|
|
let codeinput = document.getElementById("codeinput");
|
|
function set_my_code(new_code) {
|
|
let final_codeinput_string = "";
|
|
for(let i = 0; i < new_code.length; i++) {
|
|
let cur = new_code[i];
|
|
let cur_charcode = cur.charCodeAt(0);
|
|
if(cur_charcode >= "a".charCodeAt(0) && cur_charcode <= "z".charCodeAt(0)) {
|
|
final_codeinput_string += String.fromCharCode(cur_charcode + ("A".charCodeAt(0) - "a".charCodeAt(0)));
|
|
} else {
|
|
if((cur_charcode >= "A".charCodeAt(0) && cur_charcode <= "Z".charCodeAt(0)) || (cur_charcode >= "0".charCodeAt(0) && cur_charcode <= "9".charCodeAt(0))) {
|
|
final_codeinput_string += cur;
|
|
}
|
|
}
|
|
}
|
|
codeinput.value = final_codeinput_string;
|
|
}
|
|
function get_my_code() {
|
|
return codeinput.value;
|
|
}
|
|
let code_visible = false;
|
|
let input_modal = document.getElementById("inputdiv");
|
|
let pause_modal = document.getElementById("buy_menu");
|
|
let save_game_data = null;
|
|
|
|
function visibility_toggle() {
|
|
if(code_visible) {
|
|
code_visible = false;
|
|
document.getElementById("codeinput").type = "password";
|
|
document.getElementById("visibility-toggle-eye").className = "fa fa-eye-slash";
|
|
} else {
|
|
code_visible = true;
|
|
document.getElementById("codeinput").type = "text";
|
|
document.getElementById("visibility-toggle-eye").className = "fa fa-eye";
|
|
}
|
|
}
|
|
|
|
function frame(delta) {
|
|
let input_visible = input_modal.style.display === "flex";
|
|
let pause_visible = pause_modal.style.display === "flex";
|
|
|
|
if( Module.ccall('in_dialog', 'bool', [], [])) {
|
|
if(!input_visible) {
|
|
document.getElementById("inputtext").value = "";
|
|
setTimeout(function(){document.getElementById("inputtext").focus();},50); // for some reason focus doesn't work immediately here
|
|
document.getElementById("inputdiv").style.display = "flex";
|
|
}
|
|
}
|
|
|
|
if( (input_visible || pause_visible) && game_doing_input)
|
|
{
|
|
Module.ccall('stop_controlling_input', 'void', [], []);
|
|
game_doing_input = false;
|
|
}
|
|
if( !input_visible && !pause_visible && !game_doing_input)
|
|
{
|
|
Module.ccall('start_controlling_input', 'void', [], []);
|
|
game_doing_input = true;
|
|
}
|
|
|
|
window.requestAnimationFrame(frame);
|
|
}
|
|
Module.onRuntimeInitialized = () => {
|
|
window.requestAnimationFrame(frame);
|
|
}
|
|
|
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
// Use a lookup table to find the index.
|
|
const lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);
|
|
for (let i = 0; i < chars.length; i++) {
|
|
lookup[chars.charCodeAt(i)] = i;
|
|
}
|
|
const encode = (arraybuffer) => {
|
|
let bytes = new Uint8Array(arraybuffer), i, len = bytes.length, base64 = '';
|
|
for (i = 0; i < len; i += 3) {
|
|
base64 += chars[bytes[i] >> 2];
|
|
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
|
|
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
|
|
base64 += chars[bytes[i + 2] & 63];
|
|
}
|
|
if (len % 3 === 2) {
|
|
base64 = base64.substring(0, base64.length - 1) + '=';
|
|
}
|
|
else if (len % 3 === 1) {
|
|
base64 = base64.substring(0, base64.length - 2) + '==';
|
|
}
|
|
return base64;
|
|
};
|
|
const decode = (base64) => {
|
|
let bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4;
|
|
if (base64[base64.length - 1] === '=') {
|
|
bufferLength--;
|
|
if (base64[base64.length - 2] === '=') {
|
|
bufferLength--;
|
|
}
|
|
}
|
|
const arraybuffer = new ArrayBuffer(bufferLength), bytes = new Uint8Array(arraybuffer);
|
|
for (i = 0; i < len; i += 4) {
|
|
encoded1 = lookup[base64.charCodeAt(i)];
|
|
encoded2 = lookup[base64.charCodeAt(i + 1)];
|
|
encoded3 = lookup[base64.charCodeAt(i + 2)];
|
|
encoded4 = lookup[base64.charCodeAt(i + 3)];
|
|
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
|
|
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
|
|
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
|
|
}
|
|
return arraybuffer;
|
|
};
|
|
|
|
|
|
function end_dialog() {
|
|
document.getElementById("inputdiv").style.display = "none";
|
|
Module.ccall('end_text_input', 'void', ['string'], [document.getElementById("inputtext").value]);
|
|
}
|
|
|
|
function show_paywall() {
|
|
document.getElementById('buy_menu').style.display = "flex";
|
|
}
|
|
|
|
function hide_paywall() {
|
|
document.getElementById('buy_menu').style.display = "none";
|
|
}
|
|
|
|
function on_codeinput_key(event) {
|
|
set_my_code(get_my_code());
|
|
}
|
|
|
|
function save_all() {
|
|
document.cookie = get_my_code();
|
|
if (typeof(Storage) !== "undefined") {
|
|
console.log("Saving full game");
|
|
Module.ccall('dump_save_data', 'void', [], []);
|
|
localStorage.setItem("game", encode(save_game_data));
|
|
}
|
|
}
|
|
|
|
function load_all() {
|
|
set_my_code(document.cookie);
|
|
if (typeof(Storage) !== "undefined") {
|
|
console.log("Attempting read of full game...");
|
|
let read_data = localStorage.getItem("game");
|
|
if (read_data != null) {
|
|
console.log("Reading full game");
|
|
let decoded = decode(read_data);
|
|
Module.ccall('read_from_save_data', 'void', ['array', 'number'], [new Uint8Array(decoded), decoded.byteLength]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function end_without_saying()
|
|
{
|
|
document.getElementById("inputtext").value = "";
|
|
end_dialog();
|
|
}
|
|
|
|
function on_textarea_key(event) {
|
|
let final_textarea_string = "";
|
|
let cur_textarea_string = document.getElementById("inputtext").value;
|
|
let should_end = false;
|
|
for(let i = 0; i < Math.min(cur_textarea_string.length, 250); i++)
|
|
{
|
|
let cur = cur_textarea_string[i];
|
|
if(cur === "\n") {
|
|
should_end = true;
|
|
continue;
|
|
}
|
|
if(cur.charCodeAt(0) >= 255) continue; // non ascii gtfo
|
|
final_textarea_string += cur_textarea_string[i];
|
|
}
|
|
document.getElementById("inputtext").value = final_textarea_string;
|
|
if(event.key === "Enter") should_end = true;
|
|
if(event.key === "Escape")
|
|
{
|
|
document.getElementById("inputtext").value = "";
|
|
should_end = true;
|
|
}
|
|
// have a boolean flag here and call it once so the game's end dialog function isn't called twice
|
|
if(should_end) end_dialog();
|
|
}
|
|
|
|
const max_retries = 5;
|
|
let cur_id = 1; // zero is not a valid id, the zero value means no async request currently active
|
|
let generation_requests = []; // array of dictionaries with structure:
|
|
/*
|
|
{ id: number,
|
|
request: XMLHTTPRequest,
|
|
request_info: { url: string, body: string }
|
|
retries_remaining: number, // on failure, retries over and over until out of retries
|
|
}
|
|
*/
|
|
function do_checkout() {
|
|
fetch(server_url + "/checkout").then((response) => response.text()).then((text) => {
|
|
split_up = text.split("|");
|
|
if(split_up.length !== 2) {
|
|
console.log("Weird response from server, length isn't 2");
|
|
} else {
|
|
set_my_code(split_up[0])
|
|
save_all();
|
|
let url_to_go_to = split_up[1];
|
|
window.location.href = url_to_go_to;
|
|
}
|
|
}).catch((error) => {
|
|
console.log("Error doing checkout: " + error);
|
|
});
|
|
}
|
|
|
|
function resend_request(r) {
|
|
r.failed = false;
|
|
r.request = new XMLHttpRequest();
|
|
r.request.onerror = function(e) {
|
|
r.failed = true;
|
|
};
|
|
r.request.open("POST", r.request_info.url, true);
|
|
r.request.send(get_my_code() + "|" + r.request_info.body);
|
|
}
|
|
|
|
// server URL is decided in C
|
|
function set_server_url(p) {
|
|
server_url = p;
|
|
}
|
|
|
|
// Returns an integer, a "handle" into the generation request. Takes in the string prompt, and api URL
|
|
// the api url must start with `http://` or https.
|
|
function make_generation_request(p, api) {
|
|
cur_id += 1;
|
|
let to_push = {
|
|
"id": cur_id,
|
|
"request": new XMLHttpRequest(),
|
|
"retries_remaining": max_retries,
|
|
"request_info": {"url": api, "body": p},
|
|
"failed": false,
|
|
}
|
|
console.log("Making generation request with id " + to_push.id);
|
|
generation_requests.push(to_push)
|
|
resend_request(to_push)
|
|
return cur_id;
|
|
}
|
|
|
|
// returns 0 if not done yet, 1 if succeeded, 2 if failed after too many retries (i.e server down, or no internet)
|
|
// -1 if it doesn't exist
|
|
function get_generation_request_status(id) {
|
|
for(let i = 0; i < generation_requests.length; i++) {
|
|
if(generation_requests[i].id == id) {
|
|
if(generation_requests[i].failed)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
let http_status = generation_requests[i].request.status;
|
|
if(http_status == 200) {
|
|
if(generation_requests[i].request.responseText[0] === "0")
|
|
{
|
|
show_paywall();
|
|
return 2;
|
|
}
|
|
else if(generation_requests[i].request.responseText[0] === "1")
|
|
{
|
|
return 1; // done, everything ok
|
|
}
|
|
else
|
|
{
|
|
console.log("Unknown body code " + generation_requests[i].request.responseText);
|
|
}
|
|
}
|
|
else if(http_status == 0) { // not done yet
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
{ // errored
|
|
if(generation_requests[i].retries_remaining > 0) {
|
|
console.log("Retrying request");
|
|
generation_requests[i].retries_remaining -= 1;
|
|
resend_request(generation_requests[i]);
|
|
return 0;
|
|
} else {
|
|
return 2; // too many retries, failed
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function done_with_generation_request(id) {
|
|
console.log("Removing request with id " + id);
|
|
let new_generation_requests = [];
|
|
for(let i = 0; i < generation_requests.length; i++) {
|
|
if(generation_requests[i].id == id)
|
|
{
|
|
} else {
|
|
new_generation_requests.push(generation_requests[i])
|
|
}
|
|
}
|
|
generation_requests = new_generation_requests;
|
|
}
|
|
|
|
// doesn't fill string if not done yet, or the id doesn't exist
|
|
function get_generation_request_content(id) {
|
|
let to_return = "";
|
|
for(let i = 0; i < generation_requests.length; i++) {
|
|
if(generation_requests[i].id == id) {
|
|
to_return = generation_requests[i].request.responseText;
|
|
break;
|
|
}
|
|
}
|
|
return to_return.slice(1); // first character is a code that says if the request was successful or not
|
|
}
|
|
</script>
|
|
{{{ SCRIPT }}}
|
|
|
|
</body></html>
|
|
|