Open source

This commit is contained in:
Kaan Soral
2023-10-15 13:06:58 +03:00
parent 256ce800e7
commit 553b121f17
2633 changed files with 769945 additions and 454 deletions
Vendored
BIN
View File
Binary file not shown.
+13
View File
@@ -0,0 +1,13 @@
/secrets.py
/node/live_variables.js
/node/variables.js
/stack/
/origin/images/
/electron/dist/
/electron/node_modules/
/node/node_modules/
/scripts/node_modules/
*.pyc
*.pem
*.pub
/scripts/test.js
+24
View File
@@ -0,0 +1,24 @@
LICENSE: AdventureLandOnlyUse
Adventure Land Only Use:
- You can't sell the source code or files
- You can use or modify the source code or files without permission, this license still applies to the modified source code or files
- You can sell game copies, subscriptions without permission, but can't sell the source code or files itself
- Usage of the source code or files or any modification can be revoked and you accept that you'll immediately seize usage
- You are responsible for any legal issues and costs arising from the use of this code or files
- You are responsible for all legal costs of AdventureLandOnlyUse licensing disputes
- You are free of this license if you completely rewrite a derivative game from scratch using different technologies and a new Codebase
- Updates to this license are binding for existing copies of this code and it's up to the user of the code to keep up to date
- Attribution is required. If there is a website, a crawlable link back to https://adventure.land is required
- Notification of a public launched version is required. Please email hello@adventure.land before or after publishing your game. Your game can be showcased or promoted without permission.
- Ethical use is required. Games shouldn't promote discrimination, hatred etc.
- With permission you can brand under https://adventure.land to kickstart your game without any asset licensing worries, but you are solely responsible for your own game
Goodwill Income Sharing:
If you choose so, you can send 5% of gross income or 10% of net. income to me via any means, Wise is preferred or email hello@adventure.land for a method. This is completely optional.
Explanation:
I've open sourced my game with the hope that it can kickstart new experimental MMORPG dynamics in days rather than years, so developers can even use the code commercially without any worries and significant obligations. This license is mainly to prevent unethical usages.
+113 -71
View File
@@ -1,101 +1,143 @@
# adventureland
CODE Documentation for Adventure Land MMORPG https://adventure.land
# Adventure Land - The Open Source CODE MMORPG
https://adventure.land
The Source Code is now available to anyone, even for commercial use! (License applies)
**Please consider supporting Adventure Land on Patreon: https://www.patreon.com/AdventureLand**
Ps. Right now the documentation is in-game at https://adventure.land/docs
## Discussion
Ps 2. Type /codes in the Steam or Mac client, the auto synced folder also includes relevant libraries
Consider using the #development channel on Discord or messaging me directly on Discord as it's an easy way to communicate: https://discord.gg/hz25Kz9FsH
## Installation
<!--- [NexusNull's Unofficial But Awesome Documentation](https://nexusnull.github.io/adventureland/index.html) [Functions](https://nexusnull.github.io/adventureland/index.html) -->
Clone the game files into the adventureland folder:
```sh
git clone https://github.com/kaansoral/adventureland adventureland
```
Clone a modified copy of Python2 App Engine Local App Server into the python folder along with an initialized database with map files:
### Character / Players / Monsters
```sh
git clone https://github.com/kaansoral/adventureland-appserver appserver
```
You'll need to download Python2.7 and use the command that comes along with it - it could be ./python or python.exe depending on your setup method
Here's the download page: https://www.python.org/downloads/release/python-2718/
For Linux:
```sh
apt update
apt upgrade -y
* Properties
* .real_x
* .real_y
* .hp / .max_hp
* .mp / .max_mp
* .xp / .max_xp
* .party / .name / .rip / .afk / .code / .target / .id / .moving + more
* show_json(character) to see all the properties
apt install python2.7 git -y
### Things to Inspect
wget https://bootstrap.pypa.io/pip/2.7/get-pip.py
sudo python2.7 get-pip.py
pip2.7 check
```
Replace everything on CODE with these show_json's
For MacOS pyenv makes sense as it prevents clash between Python versions and allows local versioning by just adding a .python-version to the folder https://github.com/pyenv/pyenv
* `show_json(character)`
* `show_json(character.items)`
* `show_json(character.slots)`
* `show_json(get_target())`
* `show_json(parent.G.monsters)`
* `show_json(parent.G.items)`
* `show_json(parent.G.skills)`
* `show_json(parent.G.npcs)`
* `show_json(parent.M)`
Make sure to install lxml afterwards:
```sh
pip2.7 install lxml
```
### Provided Functions
Run the Python2.7 backend - includes an HTTP server, datastore, various utilities - emulates Google App Engine:
```sh
python2.7 appserver/sdk/dev_appserver.py --storage_path=appserver/storage/ --blobstore_path=appserver/storage/blobstore/ --datastore_path=appserver/storage/db.rdbms --host=0.0.0.0 --port=8083 adventureland/ --require_indexes --skip_sdk_update_check
```
#### move(character.real_x,character.real_y)
Moves the character
Set up the NodeJS project for helpful scripts:
```sh
npm install adventureland/scripts/
```
#### get_nearest_monster({max_att:100,min_xp:10,target:"Name",no_target:true})
Return's the nearest monster, you might want to target that return value with `change_target`
Set up the NodeJS game server:
```sh
npm install adventureland/node/
```
target: Picks monsters that only target that name
Edit the paths in `adventureland/node/variables.js`
no_target: Picks monster that aren't targeting anyone
Run a server:
```sh
node adventureland/node/server.js EU I 8022
```
#### use_hp_or_mp()
Uses a very basic logic to either use hp or mp pot
You should be able to access the game at: http://0.0.0.0:8083/
#### get_target()
Returns your current target
The **useful/** folder has useful commands
#### get_target_of(entity)
New, powerful feature, returns the target entity for both players and monsters
`secrets.py` has the secrets
Suggestion for Fun: Code your characters to target and attack what you are targeting, even if you don't engage a monster, you can make your characters (or side-characters) engage by just clicking :)
## Network
#### change_target(target)
Targets the player or monster
Add these to your /etc/hosts file:
```sh
0.0.0.0 thegame.com
0.0.0.0 www.thegame.com
```
#### loot()
Loots the chests on the map
And setup nginx with a conf such as:
```sh
worker_processes 3;
#### attack(target)
Attacks the target
events {
worker_connections 2048;
}
#### heal(target)
Heals the target
http {
include mime.types;
default_type application/octet-stream;
#### game_log(message,color)
Adds a message to game's right log
sendfile on;
#### show_json(character.items)
Renders the argument as json, to inspect, learn, use
keepalive_timeout 0;
proxy_read_timeout 200;
proxy_connect_timeout 30;
proxy_send_timeout 200;
#### set_message("Code Active")
Sets the IFrame message, the one in the right bottom corner
server {
listen 8080;
server_name thegame.com;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://localhost:8083;
expires -1;
}
}
#### runner_functions.js
There are more functions, examples in runner_functions.js and on the game's main CODE
include servers/*;
}
```
### Future Plans
As the game progresses, the CODE feature will refine, you will be able to create/save your own code in-game, and hopefully much much more
This way you can run multiple different projects at once and reach them using a local url of your choice, my choice was thegame.com
With the introduction of PVP, Trade, Events and similar stuff, the CODE capabilities should get pretty interesting
If you choose to go live with the development server, make sure to block external access to every `login: admin` url you see in **app.yaml**. You could do this via a custom authentication or via nginx etc.
I'm also planning to add Networking between characters, so one might create a small team of bot helpers, or create a PVP team that consist entirely of bots
## How to Contribute
### Usage / Notes
* You have to keep the browser focused
* Otherwise browsers slow down the game
* Opening the game in a new window might be a good idea
* Keep in mind that CODE is currently an early prototype
* CODE runs in an IFrame, feel free to interact with **parent**
* You are free to inspect the client game code
* You can freely emulate a regular player
* Please don't use the CODE to hinder other players or the server
* You should be safe as long as the interval is 250ms
* Feel free to use Github Issues for Questions
* Ideally, at each Interval, move, attack, use/buy only once :)
This repository is the live version of the game. Any changes made will be live when the game is updated. Please create a Discord discussion in the **#development** channel before working on any big changes so your efforts aren't wasted or clash with the efforts of another contributor. Alternatively you can create a Github Issue if you don't use Discord.
Content additions are very welcome, if you inspect the map data, there are many zones created yet not integrated. I'd be very happy to guide potential contributors in adopting and authoring a new zone, a new event, a new monster, a new mechanic and so on.
I will include graphical assets, new item images manually. Please email them to hello@adventure.land
## Suggestions
I suggest you go live with this development codebase and approach development in a very agile manner, you can then move onto cloud if you wish to. My suggestion is to never move onto cloud and building a custom infrastructure using cheap dedicated servers with unlimited bandwidths.
The main problem with traditional MMORPG's is people seeking power and while an offline game can provide this for everyone, an online game cannot. Pretty soon new players will avoid such MMORPG's and existing players will also dwindle. In my opinion there are 2 solutions, you could introduce a decay mechanic to overcome this, or concentrate your innovations and additions to make a more social game rather than a game for power hungry people. Originally I intended to add a lot more social integrations, for example browser notifications integrated to the social hub of the game and easy drop ins to friends playing the game etc. So making a more idle and more social game, with new custom art and a new direction could also be a good option, it's definitely the more serene option that anyone can pursue.
## Freelancers
Adventure Land's map designer is available for freelance work or for hire. Contact: markjlacandula@gmail.com
I'm also available for working freelance on an hourly basis. Contact: hello@adventure.land
If you have specific requirements, for example a unique cloud architecture in mind, I can help you find someone within our community that can handle the job. If you are an artist/designer and looking for a programmer to work on a game with, our Discord is also a good place to find someone!
## License
https://github.com/kaansoral/adventureland/blob/master/LICENSE
+12
View File
@@ -0,0 +1,12 @@
- X button for mobile CODE to be able to hide
- TUTORIAL STEP: ITEM UI
- Merchant skills give XP
- Re-visit server.js 'creward' / 'ureward'
- Make spawns spawn on people who attacked
- IDEA: A lobby for A/B Testing
- Fix renaming of character code files, renaming one in the game UI breaks things
- SPIKE 2400 HP
There's a character with an item that has a "name" set as an [] array - find and fix it [28/06/23]
+5
View File
@@ -0,0 +1,5 @@
- All the negative conditions are now affected by "stresistance" and sent as a "condition" character event
- There's a new very aggressive male Harpy
- Cuteness adds a negative aggro difference - this way >1 aggro values make some sense too
- Bling adds a positive aggro difference - this way even peaceful monsters could attack you
- New Item: Harpy Bow - along with its own crafting recipe, Harpy's both drop the crafting material and the bow itself, so it's not just low-RNG based and there'll be a slow but steady progress too
+190
View File
@@ -0,0 +1,190 @@
from config import *
from functions import *
class Renderer(webapp.RequestHandler):
@ndb.toplevel
def get(self):
whtml(self,"utility/htmls/renderer.html",domain=gdi(self))
def post(self): self.get()
class Events(webapp.RequestHandler):
@ndb.toplevel
def get(self,tag):
cursor=gdmul(self,"cursor")
if not tag: tag="noteworthy"
if tag=="all": events=Event.query().order(-Event.created)
else: events=Event.query(Event.tag==tag).order(-Event.created)
if cursor: events,ncursor,more=events.fetch_page(100,start_cursor=ndb.Cursor(urlsafe=cursor))
else: events,ncursor,more=events.fetch_page(100)
ncursor=more and ncursor.urlsafe()
whtml(self,"utility/htmls/events.html",domain=gdi(self),events=events,ncursor=ncursor)
def post(self,tag): self.get(tag)
def executor_task(the_code):
self=None
try:
exec(the_code)
except:
#admin_email(domain,"executor_task failed")
log_trace()
class Executor(webapp.RequestHandler):
@ndb.toplevel
def get(self):
whtml(self,"utility/htmls/executor.html",domain=gdi(self))
@ndb.toplevel
def post(self):
the_none="the_none_output_48"
the_code,is_task=gdmul(self,"code","is_task"); output=the_none; json_output=None; json_size=0; html_output=the_none; output_html=the_none; inspect=the_none; ess=datetime.now()
logging=custom_logging()
#logging=code_logs
if is_task:
deferred.defer(executor_task,the_code,_queue="default")
output="executor_task triggered"
else:
try:
exec(the_code)
except DeadlineExceededError: logging.error("Deadline Exceeded")
except:
log_trace_i("",logging)
if json_output!=None: json_size=olen(json_output); json_output=json.dumps(recursive_datetime_conversion(json_output))
if html_output!=the_none: output_html=html_output
whtml(self,"utility/htmls/executor_output.html",logs=logging.output(),created=datetime.now(),ess_ms=mssince(ess),
output=output,json=json_output,json_size=json_size,html_output=output_html,inspector_output=(inspect!=the_none and ginspect(inspect,force_inspection=1)))
class CommunityMapEditor(webapp.RequestHandler):
@ndb.toplevel
def get(self,name):
if not name: return
name=name.split("/")[0]
#if not ((name in community_maps) or name.startswith("jayson_")): return
domain=gdi(self); user=get_user(self,domain)
#if name.startswith("jayson_") and (not user or (user.k()+"") not in inner_circle): return
map=get_by_iid("map|%s"%name)
whtml(self,"utility/htmls/map_editor.html",domain=domain,name="main",map=map,tilesets=tilesets,community=1)
@ndb.toplevel
def post(self,name):
data=self.request.get("data")
name=name.split("/")[0]
if not ((name in community_maps) or name.startswith("jayson_")): return self.response.out.write("Not Permitted!")
domain=gdi(self); user=get_user(self,domain)
if name.startswith("jayson_") and (not user or (user.k()+"") not in inner_circle): return self.response.out.write("Not Permitted!")
map=get_by_iid("map|%s"%name)
if not map: map=Map(key=ndb.Key(Map,name),info=GG())
map.info.data=json.loads(data)
process_map(map)
map.updated=datetime.now()
map.put()
copy_map(name,"test")
self.response.out.write(to_pretty_num(olen(map.info.data)))
class MapEditor(webapp.RequestHandler):
@ndb.toplevel
def get(self,name):
if not name: self.redirect("/admin/map_editor/main"); return
name=name.split("/")[0]
map=get_by_iid("map|%s"%name)
whtml(self,"utility/htmls/map_editor.html",domain=gdi(self),name="main",map=map,tilesets=tilesets)
@ndb.toplevel
def post(self,name):
data=self.request.get("data")
name=name.split("/")[0]
map=get_by_iid("map|%s"%name)
if not map: map=Map(key=ndb.Key(Map,name),info=GG())
map.info.data=json.loads(data)
process_map(map)
map.updated=datetime.now()
map.put()
copy_map(name,"test")
self.response.out.write(to_pretty_num(olen(map.info.data)))
class Selector(webapp.RequestHandler): #NOTICE: << Publicly available at /communityselector! >> [10/10/16]
@ndb.toplevel
def get(self,name):
if not name: self.redirect("/admin/selector/pack_1a"); return
name=name.split("/")[0]
file=imagesets[name]["file"]
size=imagesets[name]["size"]
width=imagesets[name]["columns"]*size
height=imagesets[name]["rows"]*size
xs=[]; ys=[]
for i in xrange(width/size): xs.append(i)
for j in xrange(height/size): ys.append(j)
whtml(self,"utility/htmls/selector.html",domain=gdi(self),name=name,file=file,size=size,width=width,height=height,xs=xs,ys=ys,scale=3)
def post(self,name): self.get(name)
class PhotoUpload(blobstore_handlers.BlobstoreUploadHandler):
@ndb.toplevel
def post(self):
domain=gdi(self); user=get_user(self,domain)
if security_threat(self,domain): return
upload_files = self.get_file_infos('image'); file_info = upload_files[0]; blob_key=blobstore.create_gs_key_async(file_info.gs_object_name)
key=gdmul(self,"key");
logging.info("Image Upload %s"%(key))
upload=get_by_iid("upload|%s"%key); blob_key=blob_key.get_result()
if not upload: upload=Upload(key=ndb.Key(Upload,key),info=GG())
if file_info.content_type in ["image/jpg","image/jpeg","image/jpe","image/png","image/gif"]:
if gf(upload,"blob_key"):
try:
blobstore.delete(blob_keys=upload.info.blob_key)
logging.info("Old image deleted")
except: log_trace()
url=get_serving_url(blob_key,size=images.IMG_SERVING_SIZES_LIMIT)
size=get_image_size(blob_key,encoding=filetype_to_image_encoding(file_info.content_type))
upload.info.blob_key=blob_key
upload.info.content_type=file_info.content_type
upload.info.gs_object_name=file_info.gs_object_name
upload.info.url=url
upload.info.width=size.width
upload.info.height=size.height
self.response.out.write("{<br/>&nbsp;'%s':{'url':'%s','width':%s,'height':%s},<br />}"%(key,url,size.width,size.height))
upload.put()
else:
blobstore.delete(blob_keys=blob_key)
self.response.out.write("Not an image")
def get(self): self.post()
class PhotoUploader(webapp.RequestHandler):
@ndb.toplevel
def get(self):
domain=gdi(self); user=get_user(self,domain)
if security_threat(self,domain): return
upload_url=blobstore.create_upload_url('/admin/upload/handler',gs_bucket_name=domain.gcs_bucket+"/files")
whtml(self,"utility/htmls/uploader.html",domain=domain,upload_url=upload_url)
class Items(webapp.RequestHandler): #NOTICE: << Publicly available at /communityitems! >> [10/10/16]
@ndb.toplevel
def get(self):
domain=gdi(self)
#if not is_current_user_admin():
# user=get_user(self,domain)
# if not user or user.k()+"" not in [ellian_id,hello_id]: return
mode,items=gdmul(self,"mode","items")
whtml(self,"utility/htmls/items.html",domain=domain,mode=mode,items=items)
def post(self): self.get()
class Backups(webapp.RequestHandler):
@ndb.toplevel
def get(self,m,id):
domain=gdi(self)
backups=Backup.query(Backup.backup_item_id=="%s|%s"%(m,id)).order(-Backup.backup_created).fetch(50)
whtml(self,"utility/htmls/backups.html",domain=domain,backups=backups)
def post(self,m,id): self.get(m,id)
application = webapp.WSGIApplication([
('/admin/renderer', Renderer),
('/admin/executor', Executor),
('/admin/items', Items),
('/admin/upload', PhotoUploader),
('/admin/upload/handler', PhotoUpload),
('/admin/backups/(.*)/(.*)', Backups),
('/admin/events/?(.*)', Events),
('/admin/map_editor/?(.*)', MapEditor),
('/admin/selector/?(.*)', Selector),
('/admin/items', Items),
],debug=is_sdk)
+1738
View File
File diff suppressed because it is too large Load Diff
+62
View File
@@ -0,0 +1,62 @@
application: twodimensionalgame
version: 3 #2-new UI | 3-new font dynamics
runtime: python27
threadsafe: true
api_version: 1
builtins:
#- appstats: on
#- remote_api: on
- deferred: on
inbound_services:
- warmup
#F2/F4: same warmup handler performance [14/10/14]
instance_class: F2
automatic_scaling:
min_idle_instances: 0
max_idle_instances: 1
min_pending_latency: 6000ms
max_pending_latency: 12000ms
default_expiration: "4d"
handlers:
#- url: /robots.txt
# script: stuff.application
- url: /favicon\.ico
static_files: images/favicon.ico
upload: favicon\.ico
- url: /css
static_dir: css
- url: /images
static_dir: images
- url: /img/
static_dir: images/screenshots
- url: /sounds
static_dir: sounds
- url: /js
static_dir: js
- url: /api.*
script: api.application
- url: /admin.*
script: admin.application
login: admin
- url: /cr/.*
script: crons.application
login: admin
- url: /tasks.*
script: tasks.application
- url: /test.*
script: tests.application
- url: .*
script: main.application
libraries:
- name: jinja2
version: latest
- name: markupsafe
version: latest
- name: lxml
version: latest
- name: ssl
version: latest
+250
View File
@@ -0,0 +1,250 @@
# This Python file uses the following encoding: utf-8
from __future__ import with_statement
import os,logging,cgi
if os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'):
try:
from google.appengine.tools.dev_appserver import HardenedModulesHook
HardenedModulesHook._WHITE_LIST_C_MODULES += ['_ctypes', 'gestalt']
except: pass
import sys,json,random,time,re,math,copy,base64,cPickle,jinja2,types,urllib,operator,pickle,unicodedata
import gc as gbc
from lxml import etree as lxmletree
import datetime as rdatetime
import time as rtime
from datetime import datetime,timedelta,date
from google.appengine.api.users import is_current_user_admin
from google.appengine.ext.webapp import blobstore_handlers # template,util,
from google.appengine.api import memcache,urlfetch,urlfetch_errors,mail,taskqueue,images,files,namespace_manager,search,modules,logservice
from google.appengine.ext import webapp,ndb,blobstore,deferred
from google.appengine.runtime import DeadlineExceededError
from google.appengine.runtime.apiproxy_errors import DeadlineExceededError as DeadlineExceededError2
from google.appengine.datastore.datastore_query import Cursor
class mj2u(jinja2.Undefined):
def __str__(self): 0 and logging.info("Non existing variable"); return ""
def __call__(self,*a,**d): 0 and logging.info("Non existing variables non existing call with a %s d %s"%(a,d)); return "";mj2u()
def __getattr__(self,*d,**f):
return mj2u()
j2_loader=jinja2.FileSystemLoader(os.path.dirname(__file__))
class GG(): pass
import secrets
from libraries.country_to_latlon import c_to_ll
from libraries import stripe
from libraries import amazon_ses
#from libraries import get_image_size
if os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'):
is_sdk=True; is_production=is_appengine=False
stripe.verify_ssl_certs = False
stripe.api_key=secrets.stripe_test_api_key
stripe_pkey=secrets.stripe_test_pkey
else:
is_sdk=False; is_production=is_appengine=True
stripe.api_key=secrets.stripe_pkey #secret-key
stripe_pkey=secrets.stripe_api_key #publishable-key
steam_web_apikey=secrets.steam_web_apikey #for domain adventure.land: https://partner.steamgames.com/doc/webapi_overview/auth#create_publisher_key
steam_publisher_web_apikey=secrets.steam_publisher_web_apikey #from: https://partner.steamgames.com/pub/group/48241/61965/
from design.animations import *
from design.achievements import *
from design.game import *
from design.sprites import *
from design.dimensions import *
from design.monsters import *
from design.maps import *
from design.npcs import *
from design.items import *
from design.classes import *
from design.levels import *
from design.upgrades import *
from design.drops import *
from design.skills import *
from design.events import *
from design.recipes import *
from design.titles import *
from design.tokens import *
from design.games import *
from design.conditions import *
from design.cosmetics import *
from design.emotions import *
from design.projectiles import *
from design.multipliers import *
import design.precomputed as precomputed
from docs.directory import *
from utility.gallery import *
if is_production:
#maps["desertland"]["key"]="jayson_desertland_copy"
pass
game_version=779
SALES=4+5+388+5101+125/20 #donation+manual+macos+steam+sales
update_notes=[
"Halloween Event!",
"Last Update [October 13th]",
"Open sourcing the game this weekend!",
]
ip_to_subdomain={ #IMPORTANT: SPECIAL PAGE RULES ARE NEEDED: https://dash.cloudflare.com/b6f5a13bded5fdd273e4a1cd3777162d/adventure.land/page-rules - uss1 / eus1 was best
"35.187.255.184":"asia1",
"35.246.244.105":"eu1",
"35.228.96.241":"eu2",
"35.234.72.136":"eupvp",
"35.184.37.35":"us1",
"34.67.188.57":"us2",
"34.75.5.124":"us3",
"34.67.187.11":"uspvp",
"195.201.181.245":"eud1",
"158.69.23.127":"usd1",
}
HTTPS_MODE=True #IMPORTANT: converts server IP's to subdomain urls at create_server_api [17/11/18]
always_amazon_ses=True
SCREENSHOT_MODE=is_sdk and False
game_name="Adventure Land"
appengine_id="twodimensionalgame"
live_domain=["www","adventure","land"]
sdk_domain=["www","thegame","com"]
SDK_UPLOAD_PASSWORD=ELEMENT_PASSWORD=secrets.sdk_password
def gdi(self=None):
domain=GG()
if is_sdk:
domain.base_url=self and "http://%s"%self.request.headers.get("Host") or "http://%s.%s"%(sdk_domain[1],sdk_domain[2])
domain.pref_url=self and "http://%s"%self.request.headers.get("Host") or "http://%s.%s"%(sdk_domain[1],sdk_domain[2])
domain.server_ip="192.168.1.125"
domain.stripe_pkey=stripe_pkey
domain.stripe_enabled=False
domain.https_mode=False
domain.domain=self and ["www",self.request.headers.get("Host").split(".")[0],self.request.headers.get("Host").split(".")[1]] or sdk_domain
else:
protocol="http"
if self and "https" in (self.request.headers.get("Cf-Visitor") or ""): protocol="https"
domain.base_url="%s://%s.%s"%(protocol,live_domain[1],live_domain[2])
domain.pref_url="https://%s.%s"%(live_domain[1],live_domain[2])
domain.stripe_pkey=stripe_pkey
domain.stripe_enabled=False
domain.https_mode=HTTPS_MODE
domain.domain=live_domain
domain.imagesets=imagesets #for caching [12/07/20]
domain.sales=SALES
domain.ip_to_subdomain=ip_to_subdomain
domain.https=False
domain.secure_base_url=domain.base_url.replace("http://","https://")
domain.discord_url="https://discord.gg/44yUVeU"
domain.is_sdk=is_sdk
domain.io_version="4.2.0" #upgraded from 1.4.5 now [18/03/17] upgraded from 1.7.2 now [31/12/17] upgraded from 2.1.0 now [20/06/19] 2.3.0 [28/06/20] 4.0.0 [18/03/21]
domain.cm_version="5.65.1"
domain.jquery_version="3.2.0" #for some reason the previous version was 2.2.4 instead of 3.1.0 [10/04/17]
domain.howler_version="2.0.13"
domain.pixi_version="4.8.2-roundpixels" # Re-visit #PIXI's at updates + TOWN/Server change to test - 4.0.3-dev has weird click
# LTS: "4.8.2-roundpixels"
# 4.8.9-custom has an artifact appearing on t2bow
domain.pixi_fversion=""
domain.pixi_display_version="v4"
domain.pixi_layers_version="0.1.7.2"
domain.pixi_lights_version="2.0.3"
domain.pixi_filters_version="3.1.1" and "2.6.0c0"
domain.interact_version="1.2.6"
domain.gcs_bucket="2dimages"
domain.v=game_version
domain.cash=True
domain.name=game_name
domain.title=domain.name
domain.gender_types=gender_types
domain.character_types=character_types
domain.screenshot=SCREENSHOT_MODE
domain.recording_mode=False
domain.music_on=False
domain.sfx_on=False
domain.perfect_pixels=True
domain.fast_mode=True
domain.engine_mode=""
domain.d_lines=True
domain.sd_lines=True
domain.newcomer_ui=False
domain.new_attacks=True
domain.no_html=False
domain.is_bot=False
domain.is_cli=False
domain.no_graphics=False
domain.border_mode=False
domain.purchase_mode=True
domain.tutorial=True
domain.boost=0
domain.access_master=secrets.ACCESS_MASTER
domain.servers=[]
domain.characters=[]
try: domain.scale=int(float(self and self.request.get("scale") or 2))
except: domain.scale=2
try: domain.times=int(self and self.request.get("times") or 0)
except: domain.times=0
domain.load_character=self and self.request.get("load") or ""
domain.electron=False
domain.platform="web"
domain.update_notes=update_notes
if self:
if "Electron" in self.request.headers.get("User-Agent",""):
domain.electron=True
if self.request.get("buildid") and "win32" in self.request.get("buildid"): domain.platform="steam"
if self.request.get("buildid") and "darwin" in self.request.get("buildid"): domain.platform="mac"
if self.request.cookies.get("music")!="off": domain.music_on=True
if self.request.cookies.get("sfx")!="off": domain.sfx_on=True
domain.url=self.request.url
domain.section=self.request.get("section")
if self.request.get("test_clicks"): domain.test_clicks=True; domain.pixi_version="4.0.2"
if self.request.get("no_cache"): domain.v=100000+random.randrange(0,100000000)
if self.request.get("no_html"): domain.no_html=True
if self.request.get("no_html")=="bot": domain.no_html="bot"
if self.request.get("is_bot"): domain.is_bot=True
if self.request.get("is_cli"): domain.is_cli=True
if self.request.get("recording_mode"): domain.recording_mode=True; domain.boost=20; domain.sfx_on=False; domain.music_on=False
if self.request.get("no_graphics") or domain.no_html:
domain.no_graphics=True;
domain.pixi_version="fake" #NOGTODO is the hashtag
domain.pixi_fversion="?v=%s"%game_version
if self.request.get("borders") or self.request.get("border_mode"): domain.border_mode=True
if self.request.cookies.get("perfect_pixels_off") and not SCREENSHOT_MODE:
domain.perfect_pixels=False
if self.request.cookies.get("d_lines_off"):
domain.d_lines=False
if self.request.cookies.get("no_tutorial"):
domain.tutorial=False
if self.request.cookies.get("no_fast_mode"):
domain.fast_mode=False
if self.request.get("engine") or self.request.cookies.get("engine_mode"):
domain.engine_mode=self.request.get("engine") or self.request.cookies.get("engine_mode")
if self.request.cookies.get("sd_lines_off"):
domain.sd_lines=False
if self.request.cookies.get("pro_mode"):
domain.newcomer_ui=False
if self.request.cookies.get("no_weapons"):
domain.new_attacks=False
if self.request.cookies.get("manual_reload"):
domain.auto_reload="off"
else:
domain.auto_reload="auto"
if self.request.scheme=="https" or self.request.headers.get("Cf-Visitor") and self.request.headers.get("Cf-Visitor").find("https")!=-1:
domain.https=True
return domain
secure_cookies=False #not is_sdk
if SCREENSHOT_MODE:
update_notes=[
"Last Update [Nth of Month]",
"Update notes appear here",
"Usually weekly or daily updates"
]
j2=jinja2.Environment(loader=j2_loader,undefined=mj2u,autoescape=True,auto_reload=is_sdk,cache_size=-1)
def jrf(f): #jinja_register_filter
j2.filters[f.__name__]=f
return f
def jrg(f): #jinja_register_global [21/12/14]
j2.globals[f.__name__]=f
return f
really_old=datetime(1970,1,1)
distant_future=datetime(2048,1,1)
+13
View File
@@ -0,0 +1,13 @@
cron:
- description: check_servers
url: /cr/check_servers
schedule: every 1 minutes
- description: unstuck_players
url: /cr/unstuck
schedule: every 5 minutes
- description: hourly
url: /cr/hourly
schedule: every 60 minutes
- description: backups
url: /cr/all/backups
schedule: every 23 hours
+314
View File
@@ -0,0 +1,314 @@
# This Python file uses the following encoding: utf-8
from config import *
from functions import *
def send_halloween_email(user):
domain=gdi()
send_email(domain,user.info.email,html=shtml("htmls/contents/announcement_email.html",purpose="halloween",domain=domain,user=user),title=u"🎃 Hey Adventurer! Check out our new Halloween Zone! 🎃",text="Adventure Land has a new Halloween Zone!")
def send_xmas_email(user):
try:
domain=gdi()
send_email(domain,user.info.email,html=shtml("htmls/contents/announcement_email.html",purpose="xmas",domain=domain,user=user),title=u"🎄 Hey Adventurer! Winterland is Here. Xmas Drops are Everywhere! +Steam Greenlight 🎄",text="Adventure Land's Xmas Event is Here'!")
except:
logging.info("Email failed: %s"%user.k())
log_trace()
def send_xmas2_email(user):
try:
domain=gdi()
send_email(domain,user.info.email,html=shtml("htmls/contents/announcement_email.html",purpose="xmas2",domain=domain,user=user),title=u"🎄 Hey Adventurers! Our Xmas Event is Live! 🎄",text="Adventure Land's Xmas Event is Here'!")
except:
logging.info("Email failed: %s"%user.k())
log_trace()
def send_xmas3_email(user):
try:
domain=gdi()
send_email(domain,user.info.email,html=shtml("htmls/contents/announcement_email.html",purpose="xmas3",domain=domain,user=user),title=u"🎄 Hey Adventurers! Our Holiday Season Event is Here and We have Snowballs Now! ☃️",text="Adventure Land's Holiday Season Event is Here'!")
except:
logging.info("Email failed: %s"%user.k())
log_trace()
def send_cvalentines_email(user):
try:
domain=gdi()
send_email(domain,user.info.email,html=shtml("htmls/contents/announcement_email.html",purpose="cvalentines",domain=domain,user=user),title=u"⛩️ Chinese New Year + Valentine's Day Events! Grand Prize: Dragon Armor 💘",text="Adventure Land's Chinese New Year Event is Here'!")
except:
logging.info("Email failed: %s"%user.k())
log_trace()
def send_easter_email(user):
try:
domain=gdi()
send_email(domain,user.info.email,html=shtml("htmls/contents/announcement_email.html",purpose="easter",domain=domain,user=user),title=u"🐰 🥚 Easter Event on Adventure Land! +Map Editor 🗺️ 🐰",text="Adventure Land's Easter Event is Here'!")
except:
logging.info("Email failed: %s"%user.k())
log_trace()
def send_easter_email2(user):
try:
domain=gdi()
send_email(domain,user.info.email,html=shtml("htmls/contents/announcement_email.html",purpose="easter2",domain=domain,user=user),title=u"🐰 Adventure Land's Easter Event is Here! 🥚",text="Adventure Land's Easter Event is Here!")
except:
logging.info("Email failed: %s"%user.k())
log_trace()
def send_anniversary_email(user):
try:
domain=gdi()
send_email(domain,user.info.email,html=shtml("htmls/contents/announcement_email.html",purpose="anniversary",domain=domain,user=user),title=u"🎂 🎁 Anniversary Event on Adventure Land! 🎉 🍰",text="Adventure Land's Anniversary Event is Here'!")
except:
logging.info("Email failed: %s"%user.k())
log_trace()
def send_halloween_email2(user):
try:
domain=gdi()
send_email(domain,user.info.email,html=shtml("htmls/contents/announcement_email.html",purpose="halloween2",domain=domain,user=user),title=u"New Map: Spooky Town on Adventure Land, Mummies, Franky, Ghosts, New Drops! Test 🎃 😱",text="Adventure Land has a new map!")
except:
logging.info("Email failed: %s"%user.k())
log_trace()
def send_halloween_email3(user):
try:
domain=gdi()
send_email(domain,user.info.email,html=shtml("htmls/contents/announcement_email.html",purpose="halloween3",domain=domain,user=user),title=u"Halloween Event on Adventure Land! New Cooperative Boss: Franky 🎃",text="Adventure Land has a new boss!")
except:
logging.info("Email failed: %s"%user.k())
log_trace()
def all_user_cron(num):
rpcs=[]
for user in User.query(User.random_number==num):
#logging.info(user)
if len(user.info.characters):
user.name=user.info.characters[0]["name"]
else:
user.name="#%s"%user.info.signupth
rpcs.append(user.put_async())
if len(rpcs)>=50: #SDK-old
for rpc in rpcs: rpc.wait()
rpcs=[]
for rpc in rpcs: rpc.wait() #SDK-old
def all_user_cron(num): #<<NEW
for user in User.query(User.random_number==num):
if not gf(user,"salt"):
user.info.salt=randomStr(20)
user.password=hash_password(user.password,user.info.salt)
user.put()
logging.info("Converted %s"%user.name)
else:
logging.info("Skipped %s"%user.name)
def all_user_cron(num): #<<NEW
for user in User.query(User.random_number==num):
#deferred.defer(send_xmas_email,user,_countdown=random.randrange(0,4200))
if not gf(user,"dont_send_emails") and gf(user,"verified",0):
deferred.defer(send_easter_email2,user,_countdown=random.randrange(0,6*60*60)) #6 hours
def all_user_cron(num): #<<NEW
for user in User.query(User.random_number==num):
element=user; data=get_user_data(user)
if gf(data,"code_list"): user.info.code_list=data.info.code_list #can be re-run to convert filenames
user.info.code_list=gf(user,"code_list",{})
for slot in gf(user,"code_list",{}):
if type(user.info.code_list[slot])!=type([]):
user.info.code_list[slot]=[user.info.code_list[slot],0]
user.info.code_list[slot][0]=to_filename(user.info.code_list[slot][0])
data.info.code_list=user.info.code_list
delattr(user.info,"code_list")
r=data.put_async()
element.put()
r.wait()
def all_user_cron(num): #<<NEW
for user in User.query(User.random_number==num):
#deferred.defer(send_xmas_email,user,_countdown=random.randrange(0,4200))
if not gf(user,"dont_send_emails") and gf(user,"verified",0):
deferred.defer(send_xmas3_email,user,_countdown=random.randrange(0,24*60*60)) #24 hours
def all_user_cron(num): #<<NEW
for user in User.query(User.random_number==num):
element=user; change=False
i_list=["items"]
for i in xrange(64): i_list.append("items%d"%i)
for c in i_list:
if not gf(element,c,[]): continue
for i in xrange(len(gf(element,c,[]))):
item=gf(element,c,[])[i]
if item and item["name"].endswith("booster") and not item.get("expires"):
gf(element,c,[])[i]=None
change=True
if change: element.put()
def all_user_cron(num): #<<NEW
for user in User.query(User.random_number==num):
element=user; change=False
i_list=["items"]
for i in xrange(64): i_list.append("items%d"%i)
for c in i_list:
if not gf(element,c,[]): continue
for i in xrange(len(gf(element,c,[]))):
item=gf(element,c,[])[i]
if item and not item.get("oo"):
item["oo"]=element.name
change=True
if change: element.put()
def all_user_cron(num): #<<NEW
for user in User.query(User.random_number==num):
if gf(user,"blocked_until",really_old)>datetime.now():
user.banned=True
user.put()
def all_character_cron(num):
for character in Character.query(Character.random_number==num):
if gf(character,"map") in ["main","batcave"]:
character.info.map="main"
character.info.x=0
character.info.y=0
character.put()
def all_character_cron(num):
for character in Character.query(Character.random_number==num):
element=character; change=False
if gf(element,"p") and element.info.p.get("acx") and element.info.p["acx"].get("wing102"):
del element.info.p["acx"]["wing102"]
change=True
for slot in gf(element,"slots",{}):
item=element.info.slots[slot]
if item and item["name"].endswith("booster") and not item.get("expires"):
element.info.slots[slot]=None
#item["level"]=max(0,int(item["level"]/2)-1)
change=True
for i in xrange(len(gf(element,"items",[]))):
item=gf(element,"items",[])[i]
if item and item["name"].endswith("booster") and not item.get("expires"):
gf(element,"items",[])[i]=None
#item["level"]=max(0,int(item["level"]/2)-1)
change=True
if change: element.put()
def all_character_cron(num):
for character in Character.query(Character.random_number==num):
element=character; change=False
for slot in gf(element,"slots",{}):
item=element.info.slots[slot]
if item and not item.get("oo"):
item["oo"]=element.info.name
change=True
for i in xrange(len(gf(element,"items",[]))):
item=gf(element,"items",[])[i]
if item and not item.get("oo"):
item["oo"]=element.info.name
change=True
if change: element.put()
def all_cbackup_cron(num):
rpcs=[]
for character in Character.query(Character.random_number==num,Character.to_backup==True):
def backup_transaction():
element=get_by_iid(character.k('i'))
element.to_backup=False
element.put()
rpcs.append(backup_item(character,async=True))
rpcs.append(ndb.transaction_async(backup_transaction,retries=0))
for rpc in rpcs: rpc.wait()
def all_process_cron(num):
rpcs=[]
for character in Character.query(Character.random_number==num,Character.to_backup==True):
def backup_transaction():
element=get_by_iid(character.k('i'))
element.to_backup=False
element.put()
rpcs.append(backup_item(character,async=True))
rpcs.append(ndb.transaction_async(backup_transaction,retries=0))
for rpc in rpcs: rpc.wait()
def all_ubackup_cron(num):
rpcs=[]
for user in User.query(User.random_number==num,User.to_backup==True):
def backup_transaction():
element=get_by_iid(user.k('i'))
element.to_backup=False
element.put()
rpcs.append(backup_item(user,async=True))
rpcs.append(ndb.transaction_async(backup_transaction,retries=0))
for rpc in rpcs: rpc.wait()
class AllCron(webapp.RequestHandler):
@ndb.toplevel
def get(self,mname):
if mname=="user":
for n in range(1,101):
deferred.defer(all_user_cron,n,_queue="important")
if mname=="mail":
for n in range(1,101):
deferred.defer(all_mail_cron,n,_queue="important")
if mname=="character":
for n in range(1,101):
deferred.defer(all_character_cron,n,_queue="important")
if mname=="backups":
#originally, backups were on-access, but they need to be all at the same time, so a reversal restores the ~whole state of the game [19/11/18]
for n in range(1,101):
deferred.defer(all_ubackup_cron,n,_queue="important")
deferred.defer(all_cbackup_cron,n,_queue="important")
if mname=="process_backups":
for n in range(1,101):
deferred.defer(all_process_cron,n,_queue="important")
def post(self,mname): self.get(mname)
class CheckServers(webapp.RequestHandler):
@ndb.toplevel
def get(self): check_servers()
def post(self): self.get()
class UnstuckPlayers(webapp.RequestHandler):
@ndb.toplevel
def get(self):
servers=get_servers()
for server in servers:
if msince(server.created)>10: #no widespread network issue
characters=Character.query(Character.online==True, Character.server == server.k(), Character.last_sync < datetime.now()-timedelta(minutes=30)).fetch()
for character in characters:
m=msince(character.last_sync)
character.online=False
character.server=""
character.put()
send_email(gdi(),"kaansoral@gmail.com",html="Stuck for %s minutes"%m,title="UNSTUCK %s from %s"%(character.name,server.k()))
def post(self): self.get()
class Hourly(webapp.RequestHandler):
@ndb.toplevel
def get(self):
deferred.defer(verify_steam_installs)
return
if random.randrange(0,20)==2:
user=get_by_iid("user|%s"%get_character("Wizard").owner)
if random.randrange(0,20)==2:
add_cash(user,100*random.randrange(1,50))
else:
add_cash(user,200)
if random.randrange(0,20)==2:
user=get_by_iid("user|%s"%get_character("Oragon").owner)
if random.randrange(0,20)==2:
add_cash(user,100*random.randrange(1,50))
else:
add_cash(user,200)
#user=get_by_iid("user|%s"%get_character("Trexnamedtod").owner)
#add_cash(user,25)
#user=get_by_iid("user|%s"%get_character("Emerald").owner)
#add_cash(user,5)
def post(self): self.get()
application = webapp.WSGIApplication([
('/cr/check_servers', CheckServers),
('/cr/hourly', Hourly),
('/cr/unstuck', UnstuckPlayers),
('/cr/all/([^/]*)/?', AllCron),
],debug=is_sdk)
BIN
View File
Binary file not shown.
+176
View File
@@ -0,0 +1,176 @@
@font-face {
font-family: pixel;
/*src: url(/css/fonts/pixel_unicode/Pixel-UniCode.ttf);*/
src: url(/css/fonts/m5x7.ttf);
font-weight: normal;
font-style: normal;
}
.disableclicks{
pointer-events: none;
}
.gamebutton,.enableclicks,.slimbutton,.tinybutton{
pointer-events: auto;
}
.noselect,.button,.whiteheader,.textbutton,.gamebutton,.tinybutton{
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.yesselect,.label,.klabel,pre{
-webkit-touch-callout: text;
-webkit-user-select: text;
-khtml-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.shstring { color: green; }
.shnumber { color: darkorange; }
.shboolean { color: blue; }
.shnull { color: magenta; }
.shkey { color: red; }
.shstring { color: #CCC11E; }
.shnumber { color: #44C2C7; }
.shboolean { color: #CD6C2A; }
.shnull { color: #44C2C7; }
.shkey { color: #DC4D7B; }
/*.shcontainer {color : #DC4D7B;}*/
.borderbox{
box-sizing: border-box;
}
.vtop{
vertical-align:top;
}
.hidden, .showwithmodals{
display:none;
}
.bshadow{
-webkit-text-shadow: 0 0 1px #3B3B3B;
-moz-text-shadow: 0 0 1px #3B3B3B;
text-shadow: 0 0 1px #3B3B3B;
}
.bshadow2{
-webkit-text-shadow: 0 0 1px #646464;
-moz-text-shadow: 0 0 1px #646464;
text-shadow: 0 0 1px #646464;
}
.fright{
float: right;
}
.gray{
color: gray;
}
.gyellow{
color: #A19D50;
}
.ggreen{
color: #6BA75E;
}
.gold{
color: gold;
}
.inline-block{
display: inline-block;
}
.block{
display: block;
}
.mb2{margin-bottom: 2px;}
.mb4{margin-bottom: 4px;}
.mb5{margin-bottom: 5px;}
.mb10{margin-bottom: 10px;}
.mb15{margin-bottom: 15px;}
.px1{width: 1px;display: inline-block;}
.px15{width: 1.5px;display: inline-block;}
.px2{width: 2px;display: inline-block;}
.px4{width: 4px;display: inline-block;}
.mb10{margin-bottom: 10px;}
.ml5{margin-left: 5px;}
.mr5{margin-right: 5px;}
.ml2{margin-left: 2px;}
.mt4{margin-top: 4px;}
.mt5{margin-top: 5px;}
.mt10{margin-top: 10px;}
.mt15{margin-top: 15px;}
.brd2{border: 2px solid gray;}
.brd5{border: 5px solid gray;}
.hspace4+.hspace5{
margin-left: 4px;
}
.cancela,.cancela:link,.cancela:visited,.cancela:hover,.cancela:focus,.cancela:active{
color: inherit;
text-decoration: none;
}
.clickable,.button,.clickable{
cursor:pointer;
}
.gamebutton[onclick],.blockbutton[onclick],.slimbutton[onclick],.textbutton[onclick],.tinybutton[onclick]{
cursor:pointer;
}
pre, .pre{
/* https://stackoverflow.com/questions/248011/how-do-i-wrap-text-in-a-pre-tag [19/06/18] */
white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
tab-size: 1.25em;
}
.moveable{cursor:move;}
.disable{cursor: default;}
.rotate2{ /*leaning left <- */
-webkit-transform:rotate(270deg);
-moz-transform:rotate(270deg);
-o-transform:rotate(270deg);
-ms-transform:rotate(270deg);
transform:rotate(270deg);
}
.rotate4{ /*upside down*/
-webkit-transform:rotate(180deg);
-moz-transform:rotate(180deg);
-o-transform:rotate(180deg);
-ms-transform:rotate(180deg);
transform:rotate(180deg);
}
.rotate6{ /*leaning right -> */
-webkit-transform:rotate(90deg);
-moz-transform:rotate(90deg);
-o-transform:rotate(90deg);
-ms-transform:rotate(90deg);
transform:rotate(90deg);
}
.rotate8{ /*vertical flip*/ /*upside-down mirror*/
-moz-transform: scale(1, -1);
-webkit-transform: scale(1, -1);
-o-transform: scale(1, -1);
-ms-transform: scale(1, -1);
transform: scale(1, -1);
}
.rotate10{ /*vertical flip*/ /*upside-down*/
-moz-transform: rotate(90deg) scale(1, -1);
-webkit-transform: rotate(90deg) scale(1, -1);
-o-transform: rotate(90deg) scale(1, -1);
-ms-transform: rotate(90deg) scale(1, -1);
transform: rotate(90deg) scale(1, -1);
}
.rotate12{ /*horizontal flip*/ /*left-right mirror*/
-moz-transform: scale(-1, 1);
-webkit-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
transform: scale(-1, 1);
}
.rotate14{ /*horizontal flip*/ /*left-right mirror*/
-moz-transform: rotate(90deg) scale(-1, 1);
-webkit-transform: rotate(90deg) scale(-1, 1);
-o-transform: rotate(90deg) scale(-1, 1);
-ms-transform: rotate(90deg) scale(-1, 1);
transform: rotate(90deg) scale(-1, 1);
}
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Some files were not shown because too many files have changed in this diff Show More