Edit Game Settings via Wfi
- Several settings can be updates via wifi during game play - Some seetings code cleanup
This commit is contained in:
15
README.md
15
README.md
@@ -20,11 +20,20 @@ This was ported from the [TWANG fork](https://github.com/bdring/TWANG) by bdring
|
|||||||
- **SSID:** TWANG_AP
|
- **SSID:** TWANG_AP
|
||||||
- **Password:** esp32rocks
|
- **Password:** esp32rocks
|
||||||
- **URL:** 192.168.4.1
|
- **URL:** 192.168.4.1
|
||||||
|
- You can update these settings over wifi
|
||||||
|
- LED Brightness
|
||||||
|
- Audio Volume
|
||||||
|
- Joystick Deadzone (removes drift)
|
||||||
|
- Attack Threshold (twang sensitivity)
|
||||||
|
- Lives Per Level
|
||||||
|
|
||||||
**Coming Soon:**
|
|
||||||
|
|
||||||
- Wireless features
|
|
||||||
- ~~A wireless version of the serial port features of TWANG. I think the easiest way is to make it a Wifi access point with a simple web page interface. This allows control by any smartphone or computer with no client side work required.~~
|

|
||||||
|
|
||||||
|
Coming Soon:**
|
||||||
|
|
||||||
|
- Wireless features~~
|
||||||
- 2 Player features by linking controllers. TBD
|
- 2 Player features by linking controllers. TBD
|
||||||
- Digitized Audio
|
- Digitized Audio
|
||||||
- Currently the port uses the same square wave tones of the the Arduino version.
|
- Currently the port uses the same square wave tones of the the Arduino version.
|
||||||
|
|||||||
43
TWANG32.ino
43
TWANG32.ino
@@ -6,7 +6,7 @@
|
|||||||
TWANG was originally created by Critters
|
TWANG was originally created by Critters
|
||||||
https://github.com/Critters/TWANG
|
https://github.com/Critters/TWANG
|
||||||
|
|
||||||
It was inspired by Robin Baumgarten's Line Wobbler Game_Audio
|
It was inspired by Robin Baumgarten's Line Wobbler Game
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -287,22 +287,21 @@ void loop() {
|
|||||||
// LEVEL COMPLETE
|
// LEVEL COMPLETE
|
||||||
tickWin(mm);
|
tickWin(mm);
|
||||||
}else if(stage == BOSS_KILLED){
|
}else if(stage == BOSS_KILLED){
|
||||||
tickBossKilled(mm);
|
tickBossKilled(mm);
|
||||||
//tickComplete(mm);
|
|
||||||
} else if (stage == GAMEOVER) {
|
} else if (stage == GAMEOVER) {
|
||||||
if (stageStartTime+GAMEOVER_FADE_DURATION > mm)
|
if (stageStartTime+GAMEOVER_FADE_DURATION > mm)
|
||||||
{
|
{
|
||||||
tickGameover(mm);
|
tickGameover(mm);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FastLED.clear();
|
FastLED.clear();
|
||||||
save_game_stats(false);
|
save_game_stats(false); // boss not killed
|
||||||
//score = 0; // reset the score
|
//score = 0; // reset the score
|
||||||
levelNumber = 0;
|
levelNumber = 0;
|
||||||
lives = user_settings.lives_per_level;
|
lives = user_settings.lives_per_level;
|
||||||
loadLevel();
|
loadLevel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FastLED.show();
|
FastLED.show();
|
||||||
@@ -318,8 +317,8 @@ void loop() {
|
|||||||
void loadLevel(){
|
void loadLevel(){
|
||||||
// leave these alone
|
// leave these alone
|
||||||
updateLives();
|
updateLives();
|
||||||
cleanupLevel();
|
cleanupLevel();
|
||||||
playerAlive = 1;
|
playerAlive = 1;
|
||||||
lastLevel = false; // this gets changed on the boss level
|
lastLevel = false; // this gets changed on the boss level
|
||||||
|
|
||||||
/// Defaults...OK to change the following items in the levels below
|
/// Defaults...OK to change the following items in the levels below
|
||||||
@@ -874,6 +873,7 @@ void tickBossKilled(long mm) // boss funeral
|
|||||||
}
|
}
|
||||||
SFXcomplete();
|
SFXcomplete();
|
||||||
}else{
|
}else{
|
||||||
|
save_game_stats(true); // true = boss was killed
|
||||||
nextLevel();
|
nextLevel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1017,12 +1017,7 @@ bool inLava(int pos){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateLives(){
|
void updateLives(){
|
||||||
// Updates the life LEDs to show how many lives the player has left
|
|
||||||
//for(int i = 0; i<LIFE_LEDS; i++){
|
|
||||||
// digitalWrite(lifeLEDs[i], lives>i?HIGH:LOW);
|
|
||||||
//}
|
|
||||||
|
|
||||||
drawLives();
|
drawLives();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
71
settings.h
71
settings.h
@@ -10,20 +10,36 @@
|
|||||||
#define EEPROM_SIZE 256
|
#define EEPROM_SIZE 256
|
||||||
|
|
||||||
// LEDS
|
// LEDS
|
||||||
#define BRIGHTNESS 150
|
#define DEFAULT_BRIGHTNESS 150
|
||||||
|
#define MIN_BRIGHTNESS 10
|
||||||
|
#define MAX_BRIGHTNESS 255
|
||||||
|
|
||||||
// PLAYER
|
// PLAYER
|
||||||
const uint8_t MAX_PLAYER_SPEED = 10; // Max move speed of the player
|
const uint8_t MAX_PLAYER_SPEED = 10; // Max move speed of the player
|
||||||
const uint8_t LIVES_PER_LEVEL = 3; // default lives per level
|
const uint8_t LIVES_PER_LEVEL = 3; // default lives per level
|
||||||
|
#define MIN_LIVES_PER_LEVEL 3
|
||||||
|
#define MAX_LIVES_PER_LEVEL 9
|
||||||
|
|
||||||
// JOYSTICK
|
// JOYSTICK
|
||||||
#define JOYSTICK_ORIENTATION 1 // 0, 1 or 2 to set the axis of the joystick
|
#define JOYSTICK_ORIENTATION 1 // 0, 1 or 2 to set the axis of the joystick
|
||||||
#define JOYSTICK_DIRECTION 1 // 0/1 to flip joystick direction
|
#define JOYSTICK_DIRECTION 1 // 0/1 to flip joystick direction
|
||||||
#define ATTACK_THRESHOLD 30000 // The threshold that triggers an attack
|
#define DEFAULT_ATTACK_THRESHOLD 30000 // The threshold that triggers an attack
|
||||||
#define JOYSTICK_DEADZONE 8 // Angle to ignore
|
#define MIN_ATTACK_THRESHOLD 20000
|
||||||
|
#define MAX_ATTACK_THRESHOLD 30000
|
||||||
|
|
||||||
|
|
||||||
|
#define DEFAULT_JOYSTICK_DEADZONE 8 // Angle to ignore
|
||||||
|
#define MIN_JOYSTICK_DEADZONE 3
|
||||||
|
#define MAX_JOYSTICK_DEADZONE 12
|
||||||
|
|
||||||
// AUDIO
|
// AUDIO
|
||||||
#define MAX_VOLUME 180 // 0 to 255
|
#define DEFAULT_VOLUME 180 // 0 to 255
|
||||||
|
#define MIN_VOLUME 0
|
||||||
|
#define MAX_VOLUME 255
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#define DAC_AUDIO_PIN 25 // should be 25 or 26 only
|
#define DAC_AUDIO_PIN 25 // should be 25 or 26 only
|
||||||
|
|
||||||
enum ErrorNums{
|
enum ErrorNums{
|
||||||
@@ -78,12 +94,7 @@ settings_t user_settings;
|
|||||||
char readBuffer[READ_BUFFER_LEN];
|
char readBuffer[READ_BUFFER_LEN];
|
||||||
uint8_t readIndex = 0;
|
uint8_t readIndex = 0;
|
||||||
|
|
||||||
void settings_init() {
|
void settings_init() {
|
||||||
|
|
||||||
//if (!EEPROM.begin(EEPROM_SIZE))
|
|
||||||
//{
|
|
||||||
// Serial.println("failed to initialize EEPROM");
|
|
||||||
//}
|
|
||||||
|
|
||||||
settings_eeprom_read();
|
settings_eeprom_read();
|
||||||
show_settings_menu();
|
show_settings_menu();
|
||||||
@@ -187,11 +198,12 @@ void change_setting(char *line) {
|
|||||||
|
|
||||||
switch (param) {
|
switch (param) {
|
||||||
case 'B': // brightness
|
case 'B': // brightness
|
||||||
if(newValue >=5 && newValue <=255) {
|
if(newValue >= MIN_BRIGHTNESS && newValue <= MAX_BRIGHTNESS) {
|
||||||
user_settings.led_brightness = (uint8_t)newValue;
|
user_settings.led_brightness = (uint8_t)newValue;
|
||||||
settings_eeprom_write();
|
settings_eeprom_write();
|
||||||
delay(1000);
|
FastLED.setBrightness(user_settings.led_brightness);
|
||||||
ESP.restart(); // this one requires a restart right now
|
//delay(1000);
|
||||||
|
//ESP.restart(); // this one requires a restart right now
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
printError(ERR_SETTING_RANGE);
|
printError(ERR_SETTING_RANGE);
|
||||||
@@ -200,7 +212,7 @@ void change_setting(char *line) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'S': // sound
|
case 'S': // sound
|
||||||
if (newValue >=0 && newValue < 255)
|
if (newValue >=MIN_VOLUME && newValue <= MAX_VOLUME)
|
||||||
user_settings.audio_volume = (uint8_t)newValue;
|
user_settings.audio_volume = (uint8_t)newValue;
|
||||||
else {
|
else {
|
||||||
printError(ERR_SETTING_RANGE);
|
printError(ERR_SETTING_RANGE);
|
||||||
@@ -209,7 +221,7 @@ void change_setting(char *line) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'D': // deadzone, joystick
|
case 'D': // deadzone, joystick
|
||||||
if(newValue >=3 && newValue <=12)
|
if(newValue >=MIN_JOYSTICK_DEADZONE && newValue <=MAX_JOYSTICK_DEADZONE)
|
||||||
user_settings.joystick_deadzone = (uint8_t)newValue;
|
user_settings.joystick_deadzone = (uint8_t)newValue;
|
||||||
else {
|
else {
|
||||||
printError(ERR_SETTING_RANGE);
|
printError(ERR_SETTING_RANGE);
|
||||||
@@ -218,7 +230,7 @@ void change_setting(char *line) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'A': // attack threshold, joystick
|
case 'A': // attack threshold, joystick
|
||||||
if(newValue >=20000 && newValue <=35000)
|
if(newValue >=MIN_ATTACK_THRESHOLD && newValue <=MAX_ATTACK_THRESHOLD)
|
||||||
user_settings.attack_threshold = (uint16_t)newValue;
|
user_settings.attack_threshold = (uint16_t)newValue;
|
||||||
else {
|
else {
|
||||||
printError(ERR_SETTING_RANGE);
|
printError(ERR_SETTING_RANGE);
|
||||||
@@ -249,12 +261,12 @@ void change_setting(char *line) {
|
|||||||
void reset_settings() {
|
void reset_settings() {
|
||||||
user_settings.settings_version = SETTINGS_VERSION;
|
user_settings.settings_version = SETTINGS_VERSION;
|
||||||
|
|
||||||
user_settings.led_brightness = BRIGHTNESS;
|
user_settings.led_brightness = DEFAULT_BRIGHTNESS;
|
||||||
|
|
||||||
user_settings.joystick_deadzone = JOYSTICK_DEADZONE;
|
user_settings.joystick_deadzone = DEFAULT_JOYSTICK_DEADZONE;
|
||||||
user_settings.attack_threshold = ATTACK_THRESHOLD;
|
user_settings.attack_threshold = DEFAULT_ATTACK_THRESHOLD;
|
||||||
|
|
||||||
user_settings.audio_volume = MAX_VOLUME;
|
user_settings.audio_volume = DEFAULT_VOLUME;
|
||||||
|
|
||||||
user_settings.lives_per_level = LIVES_PER_LEVEL;
|
user_settings.lives_per_level = LIVES_PER_LEVEL;
|
||||||
|
|
||||||
@@ -297,10 +309,6 @@ void show_settings_menu() {
|
|||||||
Serial.println(" ? to show current settings");
|
Serial.println(" ? to show current settings");
|
||||||
Serial.println(" R to reset everything to defaults)");
|
Serial.println(" R to reset everything to defaults)");
|
||||||
Serial.println(" P to reset play statistics)");
|
Serial.println(" P to reset play statistics)");
|
||||||
//Serial.println(" E to write changes to eeprom)");
|
|
||||||
//Serial.println(" ! to restart with current settings");
|
|
||||||
|
|
||||||
show_game_stats();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void show_game_stats()
|
void show_game_stats()
|
||||||
@@ -314,10 +322,8 @@ void show_game_stats()
|
|||||||
Serial.print("Boss kills: ");Serial.println(user_settings.boss_kills);
|
Serial.print("Boss kills: ");Serial.println(user_settings.boss_kills);
|
||||||
}
|
}
|
||||||
|
|
||||||
void settings_eeprom_read()
|
void settings_eeprom_read() {
|
||||||
{
|
|
||||||
Serial.println("Begin EEPROM Read");
|
|
||||||
|
|
||||||
EEPROM.begin(EEPROM_SIZE);
|
EEPROM.begin(EEPROM_SIZE);
|
||||||
|
|
||||||
uint8_t ver = EEPROM.read(0);
|
uint8_t ver = EEPROM.read(0);
|
||||||
@@ -338,9 +344,11 @@ void settings_eeprom_read()
|
|||||||
temp[i] = EEPROM.read(i);
|
temp[i] = EEPROM.read(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EEPROM.end();
|
||||||
|
|
||||||
memcpy((char*)&user_settings, temp, sizeof(user_settings));
|
memcpy((char*)&user_settings, temp, sizeof(user_settings));
|
||||||
|
|
||||||
EEPROM.end();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,10 +359,7 @@ void settings_eeprom_write() {
|
|||||||
EEPROM.begin(EEPROM_SIZE);
|
EEPROM.begin(EEPROM_SIZE);
|
||||||
|
|
||||||
uint8_t temp[sizeof(user_settings)];
|
uint8_t temp[sizeof(user_settings)];
|
||||||
memcpy(temp, (uint8_t*)&user_settings, sizeof(user_settings));
|
memcpy(temp, (uint8_t*)&user_settings, sizeof(user_settings));
|
||||||
|
|
||||||
Serial.println("Writing settings...");
|
|
||||||
|
|
||||||
|
|
||||||
for (int i=0; i<sizeof(user_settings); i++)
|
for (int i=0; i<sizeof(user_settings); i++)
|
||||||
{
|
{
|
||||||
|
|||||||
107
wifi_ap.h
107
wifi_ap.h
@@ -1,10 +1,14 @@
|
|||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
const char* ssid = "TWANG_AP";
|
const char* ssid = "TWANG_AP";
|
||||||
const char* passphrase = "esp32rocks";
|
const char* passphrase = "esp32rocks";
|
||||||
|
|
||||||
WiFiServer server(80);
|
WiFiServer server(80);
|
||||||
|
|
||||||
|
char linebuf[80];
|
||||||
|
int charcount=0;
|
||||||
|
|
||||||
void ap_setup() {
|
void ap_setup() {
|
||||||
bool ret;
|
bool ret;
|
||||||
|
|
||||||
@@ -19,14 +23,13 @@ void ap_setup() {
|
|||||||
*/
|
*/
|
||||||
ret = WiFi.softAP(ssid, passphrase, 2, 0);
|
ret = WiFi.softAP(ssid, passphrase, 2, 0);
|
||||||
|
|
||||||
Serial.println("\r\nWiFi AP online ...");
|
//Serial.println("\r\nWiFi AP online ...");
|
||||||
server.begin();
|
server.begin();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendStatsPage(WiFiClient client) {
|
void sendStatsPage(WiFiClient client) {
|
||||||
Serial.println("printUploadForm");
|
|
||||||
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
|
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
|
||||||
// and a content-type so the client knows what's coming, then a blank line:
|
// and a content-type so the client knows what's coming, then a blank line:
|
||||||
client.println("HTTP/1.1 200 OK");
|
client.println("HTTP/1.1 200 OK");
|
||||||
@@ -36,6 +39,7 @@ void sendStatsPage(WiFiClient client) {
|
|||||||
client.println("<body>");
|
client.println("<body>");
|
||||||
client.println("<h1>TWANG32 Play Stats</h1>");
|
client.println("<h1>TWANG32 Play Stats</h1>");
|
||||||
client.println("<ul>");
|
client.println("<ul>");
|
||||||
|
|
||||||
|
|
||||||
client.print("<li>Games played: "); client.print(user_settings.games_played); client.println("</li>");
|
client.print("<li>Games played: "); client.print(user_settings.games_played); client.println("</li>");
|
||||||
if (user_settings.games_played > 0) { // prevent divide by 0
|
if (user_settings.games_played > 0) { // prevent divide by 0
|
||||||
@@ -44,7 +48,32 @@ void sendStatsPage(WiFiClient client) {
|
|||||||
client.print("<li>High score: "); client.print(user_settings.high_score); client.println("</li>");
|
client.print("<li>High score: "); client.print(user_settings.high_score); client.println("</li>");
|
||||||
client.print("<li>Boss kills: "); client.print(user_settings.boss_kills); client.println("</li>");
|
client.print("<li>Boss kills: "); client.print(user_settings.boss_kills); client.println("</li>");
|
||||||
|
|
||||||
client.println("</ul>");
|
client.print("<h2>Adjustable Settings </h2>");
|
||||||
|
|
||||||
|
client.print("<table>");
|
||||||
|
|
||||||
|
client.print("<tr><td>Brightness</td><td><form><input type='text' name='B' value='");
|
||||||
|
client.print(user_settings.led_brightness);
|
||||||
|
client.print ("' size='4'><input type='submit'></form></td></tr>");
|
||||||
|
|
||||||
|
client.print("<tr><td>Sound Volume</td><td><form><input type='text' name='S' value='");
|
||||||
|
client.print(user_settings.audio_volume);
|
||||||
|
client.print("' size='4'><input type='submit'></form></td></tr>");
|
||||||
|
|
||||||
|
client.print("<tr><td>Joystick Deadzone (3-12)</td><td><form><input type='text' name='D' value='");
|
||||||
|
client.print(user_settings.joystick_deadzone);
|
||||||
|
client.print("' size='4'><input type='submit'></form></td></tr>");
|
||||||
|
|
||||||
|
client.print("<tr><td>Attack Sensitivity (20000-35000)</td><td><form><input type='text' name='A' value='");
|
||||||
|
client.print(user_settings.attack_threshold);
|
||||||
|
client.print("' size='4'><input type='submit'></form></td></tr>");
|
||||||
|
|
||||||
|
client.print("<tr><td>Lives Per Level (3-9)</td><td><form><input type='text' name='L' value='");
|
||||||
|
client.print(user_settings.lives_per_level);
|
||||||
|
client.print("' size='4'><input type='submit'></form></td></tr>");
|
||||||
|
|
||||||
|
client.print("</table>");
|
||||||
|
|
||||||
client.println("</body>");
|
client.println("</body>");
|
||||||
client.println("</html>");
|
client.println("</html>");
|
||||||
client.println();
|
client.println();
|
||||||
@@ -57,9 +86,73 @@ void ap_client_check(){
|
|||||||
int stat;
|
int stat;
|
||||||
WiFiClient client = server.available(); // listen for incoming clients
|
WiFiClient client = server.available(); // listen for incoming clients
|
||||||
|
|
||||||
if (client) { // if you get a client,
|
//if (client) { // if you get a client,
|
||||||
sendStatsPage(client);
|
// sendStatsPage(client);
|
||||||
Serial.println("printUploadForm");
|
// Serial.println("printUploadForm");
|
||||||
}
|
//}
|
||||||
|
bool currentLineIsBlank = true;
|
||||||
|
|
||||||
|
while (client.connected()) {
|
||||||
|
if (client.available()) {
|
||||||
|
char c = client.read();
|
||||||
|
//Serial.write(c);
|
||||||
|
linebuf[charcount]=c;
|
||||||
|
if (charcount<sizeof(linebuf)-1)
|
||||||
|
charcount++;
|
||||||
|
|
||||||
|
if (c == '\n' && currentLineIsBlank) {
|
||||||
|
sendStatsPage(client);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c == '\n') {
|
||||||
|
// you're starting a new line
|
||||||
|
currentLineIsBlank = true;
|
||||||
|
|
||||||
|
|
||||||
|
if (strstr(linebuf,"GET /?") > 0)
|
||||||
|
{
|
||||||
|
String line = String(linebuf);
|
||||||
|
|
||||||
|
int start = line.indexOf('=', 0) + 1;
|
||||||
|
int finish = line.indexOf('H', start)-1;
|
||||||
|
String val = line.substring(start, finish);
|
||||||
|
// if it is not numeric, it will convert to 0.
|
||||||
|
// The constrain functions will make it 0 or the min value
|
||||||
|
|
||||||
|
if (strstr(linebuf,"B=") > 0){ // typically look like this "GET /?S=100 HTTP/1.1"
|
||||||
|
user_settings.led_brightness = constrain(val.toInt(), MIN_BRIGHTNESS, MAX_BRIGHTNESS);
|
||||||
|
FastLED.setBrightness(user_settings.led_brightness);
|
||||||
|
settings_eeprom_write();
|
||||||
|
}
|
||||||
|
else if (strstr(linebuf,"S=") > 0){
|
||||||
|
//String val = line.substring(start, finish);
|
||||||
|
user_settings.audio_volume = constrain(val.toInt(), MIN_VOLUME, MAX_VOLUME);
|
||||||
|
settings_eeprom_write();
|
||||||
|
}
|
||||||
|
else if (strstr(linebuf,"D=") > 0){
|
||||||
|
user_settings.joystick_deadzone = constrain(val.toInt(), MIN_JOYSTICK_DEADZONE, MAX_JOYSTICK_DEADZONE);
|
||||||
|
settings_eeprom_write();
|
||||||
|
}
|
||||||
|
else if (strstr(linebuf,"A=") > 0){
|
||||||
|
user_settings.attack_threshold = constrain(val.toInt(), MIN_ATTACK_THRESHOLD, MAX_ATTACK_THRESHOLD);
|
||||||
|
settings_eeprom_write();
|
||||||
|
}
|
||||||
|
else if (strstr(linebuf,"L=") > 0){
|
||||||
|
user_settings.lives_per_level = constrain(val.toInt(), MIN_LIVES_PER_LEVEL, MAX_LIVES_PER_LEVEL);
|
||||||
|
settings_eeprom_write();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// you're starting a new line
|
||||||
|
currentLineIsBlank = true;
|
||||||
|
memset(linebuf,0,sizeof(linebuf));
|
||||||
|
charcount=0;
|
||||||
|
} else if (c != '\r') {
|
||||||
|
// you've gotten a character on the current line
|
||||||
|
currentLineIsBlank = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user