Merge branches

This commit is contained in:
ToontownStride 2015-11-14 16:01:26 -05:00
commit 31bb2cdb75
1680 changed files with 427855 additions and 13 deletions

26
.gitignore vendored Executable file
View file

@ -0,0 +1,26 @@
# Python artifacts
*.pyc
*.pyo
# Batch
*.bat
# PyCharm
.idea
# Mac directory info
.DS_Store
# Shortcuts
*.lnk
# Git
*.rej
# Local config
dependencies/config/local.prc
# PyMongo
bson
gridfs
pymongo

9
.gitmodules vendored Normal file
View file

@ -0,0 +1,9 @@
[submodule "build/nirai/src"]
path = build/nirai/src
url = https://github.com/nirai-compiler/src
[submodule "build/nirai/panda3d"]
path = build/nirai/panda3d
url = https://github.com/nirai-compiler/panda3d
[submodule "build/nirai/python"]
path = build/nirai/python
url = https://github.com/nirai-compiler/python

View file

@ -1,13 +0,0 @@
# Toontown Stride
September build of Toontown Stride - open sourced and free to use.
## Tired of waiting for keys?
No need to worry! The official Toontown Stride team welcomes you to **build off any files listed inside the repository after you clone.**
## Why do this?
Since Toontown Stride was originally meant to be a community-based server, we decided to make it *truly* community based on behalf of the original contributers to the source.
##Is this version safe?
Feel free to scan yourself and give us feedback on your findings!
#Credit goes to Mr. Batty and Alexalexer for this build of the source.

3
build/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
libpandadna/
built/
*.pdb

72
build/data/NiraiStart.py Normal file
View file

@ -0,0 +1,72 @@
from panda3d.core import *
import __builtin__, os, sys
import aes
import niraidata
# Config
prc = niraidata.CONFIG
iv, key, prc = prc[:16], prc[16:32], prc[32:]
prc = aes.decrypt(prc, key, iv)
for line in prc.split('\n'):
line = line.strip()
if line:
loadPrcFileData('nirai config', line)
del prc
del iv
del key
# DC
__builtin__.dcStream = StringStream()
dc = niraidata.DC
iv, key, dc = dc[:16], dc[16:32], dc[32:]
dc = aes.decrypt(dc, key, iv)
dcStream.setData(dc)
del dc
del iv
del key
# Resources
# TO DO: Sign and verify the phases to prevent editing.
vfs = VirtualFileSystem.getGlobalPtr()
mfs = [3, 3.5, 4, 5, 5.5, 6, 7, 8, 9, 10, 11, 12, 13]
abort = False
for mf in mfs:
filename = 'resources/default/phase_%s.mf' % mf
if not os.path.isfile(filename):
print 'Phase %s not found' % filename
abort = True
break
mf = Multifile()
mf.openRead(filename)
if not vfs.mount(mf, '/', 0):
print 'Unable to mount %s' % filename
abort = True
break
# Packs
pack = os.environ.get('TT_STRIDE_CONTENT_PACK')
import glob
if pack and pack != 'default':
print 'Loading content pack', pack
for file in glob.glob('resources/%s/*.mf' % pack):
mf = Multifile()
mf.openReadWrite(Filename(file))
names = mf.getSubfileNames()
for name in names:
ext = os.path.splitext(name)[1]
if ext not in ['.jpg', '.jpeg', '.ogg', '.rgb']:
mf.removeSubfile(name)
vfs.mount(mf, Filename('/'), 0)
if not abort:
# Run
import toontown.toonbase.ToontownStart

3
build/linux/aes-to-so.sh Executable file
View file

@ -0,0 +1,3 @@
gcc -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -fPIC -DMAJOR_VERSION=1 -DMINOR_VERSION=0 -I/usr/include -I/usr/include/python2.7 -lstdc++ -lssl -lcrypto -c ../nirai/src/aes.cxx -c -o ../nirai/src/aes.o
gcc -shared ../nirai/src/aes.o -L/usr/local/lib -lstdc++ -lssl -lcrypto -o ../nirai/src/aes.so

View file

@ -0,0 +1,2 @@
cd ../nirai/panda3d
python2.7 makepanda/makepanda.py --everything --no-contrib --no-fmodex --no-physx --no-bullet --no-pview --no-pandatool --no-swscale --no-swresample --no-speedtree --no-vrpn --no-artoolkit --no-opencv --no-directcam --no-vision --no-rocket --no-awesomium --no-deploytools --no-skel --no-ffmpeg --no-eigen --static

141
build/make.py Normal file
View file

@ -0,0 +1,141 @@
from panda3d.core import *
import argparse, struct
import sys, glob
import os
sys.path.append('nirai/src')
from niraitools import *
parser = argparse.ArgumentParser()
parser.add_argument('--compile-cxx', '-c', action='store_true',
help='Compile the CXX codes and generate stride.exe into built.')
parser.add_argument('--make-nri', '-n', action='store_true',
help='Generate stride NRI.')
parser.add_argument('--make-mfs', '-m', action='store_true',
help='Make multifiles')
args = parser.parse_args()
if not os.path.exists('built'):
os.mkdir('built')
def niraicall_obfuscate(code):
# We'll obfuscate if len(code) % 4 == 0
# This way we make sure both obfuscated and non-obfuscated code work.
if len(code) % 4:
return False, None
# Reverse
code = code[::-1]
# XOR
key = ['B', 'A', 'Q', 'J', 'R', 'P', 'Z', 'P', 'A', 'H', 'U', 'T']
output = []
for i in range(len(code)):
xor_num = ord(code[i]) ^ ord(key[i % len(key)])
output.append(chr(xor_num))
code = ''.join(output)
return True, code
niraimarshal.niraicall_obfuscate = niraicall_obfuscate
class StridePackager(NiraiPackager):
HEADER = 'TTSTRIDE'
BASEDIR = '..' + os.sep
def __init__(self, outfile, configPath=None):
NiraiPackager.__init__(self, outfile)
self.__manglebase = self.get_mangle_base(self.BASEDIR)
self.add_panda3d_dirs()
self.add_default_lib()
self.globalConfigPath = configPath
def add_source_dir(self, dir):
self.add_directory(self.BASEDIR + dir, mangler=self.__mangler)
def add_data_file(self, file):
mb = self.get_mangle_base('data/')
self.add_file('data/%s.py' % file, mangler=lambda x: x[mb:])
def __mangler(self, name):
if name.endswith('AI') or name.endswith('UD') or name in ('ToontownAIRepository', 'ToontownUberRepository',
'ToontownInternalRepository', 'ServiceStart'):
if not 'NonRepeatableRandomSource' in name:
return ''
return name[self.__manglebase:].strip('.')
def generate_niraidata(self):
print 'Generating niraidata'
if self.globalConfigPath is not None:
config = self.get_file_contents(self.globalConfigPath)
else:
config = self.get_file_contents('../dependencies/config/general.prc')
config += '\n\n' + self.get_file_contents('../dependencies/config/release/qa.prc')
config_iv = self.generate_key(16)
config_key = self.generate_key(16)
config = config_iv + config_key + aes.encrypt(config, config_key, config_iv)
niraidata = 'CONFIG = %r' % config
# DC
niraidata += '\nDC = %r' % self.get_file_contents('../dependencies/astron/dclass/stride.dc', True)
self.add_module('niraidata', niraidata, compile=True)
def process_modules(self):
# TODO: Compression
dg = Datagram()
dg.addUint32(len(self.modules))
for moduleName in self.modules:
data, size = self.modules[moduleName]
dg.addString(moduleName)
dg.addInt32(size)
dg.appendData(data)
data = dg.getMessage()
iv = self.generate_key(16)
key = self.generate_key(16)
fixed_key = ''.join(chr((i ^ (7 * i + 16)) % ((i + 5) * 3)) for i in xrange(16))
fixed_iv = ''.join(chr((i ^ (2 * i + 53)) % ((i + 9) * 6)) for i in xrange(16))
securekeyandiv = aes.encrypt(iv + key, fixed_key, fixed_iv)
return securekeyandiv + aes.encrypt(data, key, iv)
# Compile the engine
if args.compile_cxx:
compiler = NiraiCompiler('stride.exe', libs=set(glob.glob('libpandadna/libpandadna.dir/Release/*.obj')))
compiler.add_nirai_files()
compiler.add_source('src/stride.cxx')
compiler.run()
# Compile the game data
if args.make_nri:
pkg = StridePackager('built/TTSData.bin')
pkg.add_source_dir('otp')
pkg.add_source_dir('toontown')
pkg.add_data_file('NiraiStart')
pkg.generate_niraidata()
pkg.write_out()
if args.make_mfs:
os.chdir('../resources')
cmd = ''
for phasenum in ['3', '3.5', '4', '5', '5.5', '6', '7', '8', '9', '10', '11', '12', '13']:
print 'phase_%s' % (phasenum)
cmd = 'multify -cf ../build/built/resources/default/phase_%s.mf phase_%s' % (phasenum, phasenum)
p = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr)
v = p.wait()
if v != 0:
print 'The following command returned non-zero value (%d): %s' % (v, cmd[:100] + '...')
sys.exit(1)

1
build/nirai/panda3d Submodule

@ -0,0 +1 @@
Subproject commit 510b6b2b3fef1674611e4666b45db949868a8869

1
build/nirai/python Submodule

@ -0,0 +1 @@
Subproject commit d24aeac557165f986373fa6d59de128902b74112

1
build/nirai/src Submodule

@ -0,0 +1 @@
Subproject commit 936977760bfac218f634533dd4d926ad7e8e2092

158
build/src/stride.cxx Normal file
View file

@ -0,0 +1,158 @@
#include "nirai.h"
#include <datagram.h>
#include <datagramIterator.h>
#include <algorithm>
#include <string>
#include <compress_string.h>
extern "C" __declspec(dllexport) void initlibpandadna();
void init_libpandadna();
const char* header = "TTSTRIDE";
const int header_size = 8;
const int key_and_iv_size = 16;
int niraicall_onPreStart(int argc, char* argv[])
{
return 0;
}
int niraicall_onLoadGameData()
{
fstream gd;
// Open the file
gd.open("TTSData.bin", ios_base::in | ios_base::binary);
if (!gd.is_open())
{
std::cerr << "Unable to open game file!" << std::endl;
return 1;
}
// Check the header
char* read_header = new char[header_size];
gd.read(read_header, header_size);
if (memcmp(header, read_header, header_size))
{
std::cerr << "Invalid header" << std::endl;
return 1;
}
delete[] read_header;
// Decrypt
std::stringstream ss;
ss << gd.rdbuf();
gd.close();
std::string brawdata = ss.str();
// Decrypted the encrypted key and iv
std::string enckeyandiv = brawdata.substr(0, (key_and_iv_size * 2) + key_and_iv_size);
unsigned char* deckeyandiv = new unsigned char[enckeyandiv.size()];
unsigned char* fixed_key = new unsigned char[key_and_iv_size];
unsigned char* fixed_iv = new unsigned char[key_and_iv_size];
// Create fixed key and iv
for (int i = 0; i < key_and_iv_size; ++i)
fixed_key[i] = (i ^ (7 * i + 16)) % ((i + 5) * 3);
for (int i = 0; i < key_and_iv_size; ++i)
fixed_iv[i] = (i ^ (2 * i + 53)) % ((i + 9) * 6);
int deckeyandivsize = AES_decrypt((unsigned char*)enckeyandiv.c_str(), enckeyandiv.size(), fixed_key, fixed_iv, deckeyandiv);
delete[] fixed_key;
delete[] fixed_iv;
unsigned char* key = new unsigned char[key_and_iv_size];
unsigned char* iv = new unsigned char[key_and_iv_size];
// Move the decrypted key and iv into their subsequent char
for (int i = 0; i < key_and_iv_size; ++i)
iv[i] = deckeyandiv[i];
for (int i = 0; i < key_and_iv_size; ++i)
key[i] = deckeyandiv[i + key_and_iv_size];
delete[] deckeyandiv;
// Decrypt the game data
std::string rawdata = brawdata.substr((key_and_iv_size * 2) + key_and_iv_size);
unsigned char* decrypted_data = new unsigned char[rawdata.size()];
int decsize = AES_decrypt((unsigned char*)rawdata.c_str(), rawdata.size(), key, iv, decrypted_data); // Assumes no error
delete[] key;
delete[] iv;
// Read
// TODO: Compression
Datagram dg(decrypted_data, decsize);
DatagramIterator dgi(dg);
unsigned int num_modules = dgi.get_uint32();
_frozen* fzns = new _frozen[num_modules + 1];
std::string module, data;
int size;
for (unsigned int i = 0; i < num_modules; ++i)
{
module = dgi.get_string();
size = dgi.get_int32();
data = dgi.extract_bytes(abs(size));
char* name = new char[module.size() + 1];
memcpy(name, module.c_str(), module.size());
memset(&name[module.size()], 0, 1);
unsigned char* code = new unsigned char[data.size()];
memcpy(code, data.c_str(), data.size());
_frozen fz;
fz.name = name;
fz.code = code;
fz.size = size;
memcpy(&fzns[i], &fz, sizeof(_frozen));
}
nassertd(dgi.get_remaining_size() == 0)
{
std::cerr << "Corrupted data!" << std::endl;
return 1;
}
delete[] decrypted_data;
memset(&fzns[num_modules], 0, sizeof(_frozen));
PyImport_FrozenModules = fzns;
// libpandadna
init_libpandadna();
initlibpandadna();
return 0;
}
extern "C" PyObject* niraicall_deobfuscate(char* code, Py_ssize_t size)
{
std::string codestr(code, size);
const char key[12] = {'B', 'A', 'Q', 'J', 'R', 'P', 'Z', 'P', 'A', 'H', 'U', 'T'};
std::string output = codestr;
for (int i = 0; i < codestr.size(); i++)
output[i] = codestr[i] ^ key[i % (sizeof(key) / sizeof(char))];
std::reverse(output.begin(), output.end());
return PyString_FromStringAndSize(output.data(), size);
}

2
dependencies/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.json
backups/

3
dependencies/astron/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Exclude the debug builds of Astron.
astrond_debug
astrond_debug.exe

BIN
dependencies/astron/astrond vendored Executable file

Binary file not shown.

BIN
dependencies/astron/astrond.exe vendored Executable file

Binary file not shown.

View file

@ -0,0 +1,42 @@
daemon:
name: Client Agent #0
general:
eventlogger: 127.0.0.1:7198
dc_files:
- ../dclass/stride.dc
uberdogs:
- class: ClientServicesManager
id: 4665
anonymous: true
- class: ChatAgent
id: 4681
anonymous: false
- class: FriendManager
id: 4501
anonymous: false
- class: TTSFriendsManager
id: 4666
anonymous: false
- class: GlobalPartyManager
id: 4477
anonymous: false
messagedirector:
connect: 127.0.0.1:7100
roles:
- type: clientagent
bind: 0.0.0.0:7199
version: "stride-dev"
client:
relocate: true
add_interest: enabled
channels:
min: 1000000000
max: 1000001000

View file

@ -0,0 +1,42 @@
daemon:
name: Client Agent #1
general:
eventlogger: 127.0.0.1:7198
dc_files:
- ../dclass/stride.dc
uberdogs:
- class: ClientServicesManager
id: 4665
anonymous: true
- class: ChatAgent
id: 4681
anonymous: false
- class: FriendManager
id: 4501
anonymous: false
- class: TTSFriendsManager
id: 4666
anonymous: false
- class: GlobalPartyManager
id: 4477
anonymous: false
messagedirector:
connect: 127.0.0.1:7100
roles:
- type: clientagent
bind: 0.0.0.0:7299
version: "stride-dev"
client:
relocate: true
add_interest: enabled
channels:
min: 1000001001
max: 1000002000

View file

@ -0,0 +1,42 @@
daemon:
name: Client Agent #2
general:
eventlogger: 127.0.0.1:7198
dc_files:
- ../dclass/stride.dc
uberdogs:
- class: ClientServicesManager
id: 4665
anonymous: true
- class: ChatAgent
id: 4681
anonymous: false
- class: FriendManager
id: 4501
anonymous: false
- class: TTSFriendsManager
id: 4666
anonymous: false
- class: GlobalPartyManager
id: 4477
anonymous: false
messagedirector:
connect: 127.0.0.1:7100
roles:
- type: clientagent
bind: 0.0.0.0:7399
version: "stride-dev"
client:
relocate: true
add_interest: enabled
channels:
min: 1000002001
max: 1000003000

View file

@ -0,0 +1,42 @@
daemon:
name: Client Agent #3
general:
eventlogger: 127.0.0.1:7198
dc_files:
- ../dclass/stride.dc
uberdogs:
- class: ClientServicesManager
id: 4665
anonymous: true
- class: ChatAgent
id: 4681
anonymous: false
- class: FriendManager
id: 4501
anonymous: false
- class: TTSFriendsManager
id: 4666
anonymous: false
- class: GlobalPartyManager
id: 4477
anonymous: false
messagedirector:
connect: 127.0.0.1:7100
roles:
- type: clientagent
bind: 0.0.0.0:7499
version: "stride-dev"
client:
relocate: true
add_interest: enabled
channels:
min: 1000003001
max: 1000004000

View file

@ -0,0 +1,42 @@
daemon:
name: Client Agent #4
general:
eventlogger: 127.0.0.1:7198
dc_files:
- ../dclass/stride.dc
uberdogs:
- class: ClientServicesManager
id: 4665
anonymous: true
- class: ChatAgent
id: 4681
anonymous: false
- class: FriendManager
id: 4501
anonymous: false
- class: TTSFriendsManager
id: 4666
anonymous: false
- class: GlobalPartyManager
id: 4477
anonymous: false
messagedirector:
connect: 127.0.0.1:7100
roles:
- type: clientagent
bind: 0.0.0.0:7599
version: "stride-dev"
client:
relocate: true
add_interest: enabled
channels:
min: 1000004001
max: 1000005000

View file

@ -0,0 +1,42 @@
daemon:
name: Client Agent #5
general:
eventlogger: 127.0.0.1:7198
dc_files:
- ../dclass/stride.dc
uberdogs:
- class: ClientServicesManager
id: 4665
anonymous: true
- class: ChatAgent
id: 4681
anonymous: false
- class: FriendManager
id: 4501
anonymous: false
- class: TTSFriendsManager
id: 4666
anonymous: false
- class: GlobalPartyManager
id: 4477
anonymous: false
messagedirector:
connect: 127.0.0.1:7100
roles:
- type: clientagent
bind: 0.0.0.0:7699
version: "stride-dev"
client:
relocate: true
add_interest: enabled
channels:
min: 1000005001
max: 1000006000

View file

@ -0,0 +1,42 @@
daemon:
name: Client Agent #6
general:
eventlogger: 127.0.0.1:7198
dc_files:
- ../dclass/stride.dc
uberdogs:
- class: ClientServicesManager
id: 4665
anonymous: true
- class: ChatAgent
id: 4681
anonymous: false
- class: FriendManager
id: 4501
anonymous: false
- class: TTSFriendsManager
id: 4666
anonymous: false
- class: GlobalPartyManager
id: 4477
anonymous: false
messagedirector:
connect: 127.0.0.1:7100
roles:
- type: clientagent
bind: 0.0.0.0:7799
version: "stride-dev"
client:
relocate: true
add_interest: enabled
channels:
min: 1000006001
max: 1000007000

View file

@ -0,0 +1,42 @@
daemon:
name: Client Agent #7
general:
eventlogger: 127.0.0.1:7198
dc_files:
- ../dclass/stride.dc
uberdogs:
- class: ClientServicesManager
id: 4665
anonymous: true
- class: ChatAgent
id: 4681
anonymous: false
- class: FriendManager
id: 4501
anonymous: false
- class: TTSFriendsManager
id: 4666
anonymous: false
- class: GlobalPartyManager
id: 4477
anonymous: false
messagedirector:
connect: 127.0.0.1:7100
roles:
- type: clientagent
bind: 0.0.0.0:7899
version: "stride-dev"
client:
relocate: true
add_interest: enabled
channels:
min: 1000007001
max: 1000008000

64
dependencies/astron/config/cluster.yml vendored Normal file
View file

@ -0,0 +1,64 @@
daemon:
name: Developer Cluster
general:
eventlogger: 127.0.0.1:7198
dc_files:
- ../dclass/stride.dc
messagedirector:
bind: 127.0.0.1:7100
uberdogs:
- class: ClientServicesManager
id: 4665
anonymous: true
- class: ChatAgent
id: 4681
anonymous: false
- class: FriendManager
id: 4501
anonymous: false
- class: TTSFriendsManager
id: 4666
anonymous: false
- class: GlobalPartyManager
id: 4477
anonymous: false
roles:
- type: clientagent
bind: 0.0.0.0:7199
version: "tts-dev"
client:
relocate: true
add_interest: enabled
channels:
min: 1000000000
max: 1000001000
- type: stateserver
control: 4002
- type: database
control: 4003
generate:
min: 100000000
max: 399999999
backend:
type: yaml
directory: ../databases/astrondb
- type: dbss
database: 4003
ranges:
- min: 100000000
max: 399999999
- type: eventlogger
bind: 127.0.0.1:7198
output: ../logs/events-%y%m%d_%H%M%S.log

View file

@ -0,0 +1,7 @@
daemon:
name: Event Logger
roles:
- type: eventlogger
bind: 127.0.0.1:7198
output: ../logs/events-%y%m%d_%H%M%S.log

View file

@ -0,0 +1,64 @@
daemon:
name: Developer Cluster
general:
eventlogger: 0.0.0.0:7198
dc_files:
- ../dclass/stride.dc
messagedirector:
bind: 0.0.0.0:7100
uberdogs:
- class: ClientServicesManager
id: 4665
anonymous: true
- class: ChatAgent
id: 4681
anonymous: false
- class: FriendManager
id: 4501
anonymous: false
- class: TTSFriendsManager
id: 4666
anonymous: false
- class: GlobalPartyManager
id: 4477
anonymous: false
roles:
- type: clientagent
bind: 0.0.0.0:7199
version: "tts-dev"
client:
relocate: true
add_interest: enabled
channels:
min: 1000000000
max: 1000001000
- type: stateserver
control: 4002
- type: database
control: 4003
generate:
min: 100000000
max: 399999999
backend:
type: mongodb
server: mongodb://127.0.0.1:27017/stride
- type: dbss
database: 4003
ranges:
- min: 100000000
max: 399999999
- type: eventlogger
bind: 0.0.0.0:7198
output: ../logs/events-%y%m%d_%H%M%S.log

View file

@ -0,0 +1,29 @@
daemon:
name: Production Cluster
general:
eventlogger: 127.0.0.1:7198
dc_files:
- ../dclass/stride.dc
messagedirector:
bind: 127.0.0.1:7100
roles:
- type: stateserver
control: 4002
- type: database
control: 4003
generate:
min: 100000000
max: 399999999
backend:
type: mongodb
server: mongodb://127.0.0.1:27017/stride
- type: dbss
database: 4003
ranges:
- min: 100000000
max: 399999999

20
dependencies/astron/config/uberdogs.yml vendored Executable file
View file

@ -0,0 +1,20 @@
uberdogs:
- class: ClientServicesManager
id: 4665
anonymous: true
- class: ChatAgent
id: 4681
anonymous: false
- class: FriendManager
id: 4501
anonymous: false
- class: TTSFriendsManager
id: 4666
anonymous: false
- class: GlobalPartyManager
id: 4477
anonymous: false

View file

@ -0,0 +1,35 @@
#!/bin/sh
cd ../..
export DYLD_LIBRARY_PATH=`pwd`/Libraries.bundle
export DYLD_FRAMEWORK_PATH="Frameworks"
# Define some constants for our AI server:
MAX_CHANNELS=999999
STATESERVER=4002
ASTRON_IP="127.0.0.1:7100"
EVENTLOGGER_IP="127.0.0.1:7198"
# Get the user input:
read -p "District name (DEFAULT: Nuttyboro): " DISTRICT_NAME
DISTRICT_NAME=${DISTRICT_NAME:-Nuttyboro}
read -p "Base channel (DEFAULT: 401000000): " BASE_CHANNEL
BASE_CHANNEL=${BASE_CHANNEL:-401000000}
echo "==============================="
echo "Starting Toontown Stride AI server..."
echo "District name: $DISTRICT_NAME"
echo "Base channel: $BASE_CHANNEL"
echo "Max channels: $MAX_CHANNELS"
echo "State Server: $STATESERVER"
echo "Astron IP: $ASTRON_IP"
echo "Event Logger IP: $EVENTLOGGER_IP"
echo "==============================="
while [ true ]
do
ppython -m toontown.ai.ServiceStart --base-channel $BASE_CHANNEL \
--max-channels $MAX_CHANNELS --stateserver $STATESERVER \
--astron-ip $ASTRON_IP --eventlogger-ip $EVENTLOGGER_IP \
--district-name $DISTRICT_NAME
done

View file

@ -0,0 +1,3 @@
#!/bin/sh
cd ..
./astrond --loglevel info config/cluster.yml

View file

@ -0,0 +1,31 @@
#!/bin/sh
cd ../..
export DYLD_LIBRARY_PATH=`pwd`/Libraries.bundle
export DYLD_FRAMEWORK_PATH="Frameworks"
# Define some constants for our AI server:
MAX_CHANNELS=999999
STATESERVER=4002
ASTRON_IP="127.0.0.1:7100"
EVENTLOGGER_IP="127.0.0.1:7198"
# Get the user input:
read -p "Base channel (DEFAULT: 1000000): " BASE_CHANNEL
BASE_CHANNEL=${BASE_CHANNEL:-1000000}
echo "==============================="
echo "Starting Toontown Stride UberDOG server..."
echo "Base channel: $BASE_CHANNEL"
echo "Max channels: $MAX_CHANNELS"
echo "State Server: $STATESERVER"
echo "Astron IP: $ASTRON_IP"
echo "Event Logger IP: $EVENTLOGGER_IP"
echo "==============================="
while [ true ]
do
ppython -m toontown.uberdog.ServiceStart --base-channel $BASE_CHANNEL \
--max-channels $MAX_CHANNELS --stateserver $STATESERVER \
--astron-ip $ASTRON_IP --eventlogger-ip $EVENTLOGGER_IP
done

View file

@ -0,0 +1 @@
*.db

View file

@ -0,0 +1,2 @@
*
!.gitignore

3238
dependencies/astron/dclass/stride.dc vendored Normal file

File diff suppressed because it is too large Load diff

BIN
dependencies/astron/libeay32.dll vendored Executable file

Binary file not shown.

View file

@ -0,0 +1,32 @@
#!/bin/sh
cd ../..
# Define some constants for our AI server:
MAX_CHANNELS=999999
STATESERVER=4002
ASTRON_IP="127.0.0.1:7100"
EVENTLOGGER_IP="127.0.0.1:7198"
# Get the user input:
read -p "District name (DEFAULT: Nuttyboro): " DISTRICT_NAME
DISTRICT_NAME=${DISTRICT_NAME:-Nuttyboro}
read -p "Base channel (DEFAULT: 401000000): " BASE_CHANNEL
BASE_CHANNEL=${BASE_CHANNEL:-401000000}
echo "==============================="
echo "Starting Toontown Stride AI server..."
echo "District name: $DISTRICT_NAME"
echo "Base channel: $BASE_CHANNEL"
echo "Max channels: $MAX_CHANNELS"
echo "State Server: $STATESERVER"
echo "Astron IP: $ASTRON_IP"
echo "Event Logger IP: $EVENTLOGGER_IP"
echo "==============================="
while [ true ]
do
/usr/bin/python2 -m toontown.ai.ServiceStart --base-channel $BASE_CHANNEL \
--max-channels $MAX_CHANNELS --stateserver $STATESERVER \
--astron-ip $ASTRON_IP --eventlogger-ip $EVENTLOGGER_IP \
--district-name $DISTRICT_NAME
done

View file

@ -0,0 +1,3 @@
#!/bin/sh
cd ..
./astrond --loglevel info config/cluster.yml

View file

@ -0,0 +1,28 @@
#!/bin/sh
cd ../..
# Define some constants for our AI server:
MAX_CHANNELS=999999
STATESERVER=4002
ASTRON_IP="127.0.0.1:7100"
EVENTLOGGER_IP="127.0.0.1:7198"
# Get the user input:
read -p "Base channel (DEFAULT: 1000000): " BASE_CHANNEL
BASE_CHANNEL=${BASE_CHANNEL:-1000000}
echo "==============================="
echo "Starting Toontown Stride UberDOG server..."
echo "Base channel: $BASE_CHANNEL"
echo "Max channels: $MAX_CHANNELS"
echo "State Server: $STATESERVER"
echo "Astron IP: $ASTRON_IP"
echo "Event Logger IP: $EVENTLOGGER_IP"
echo "==============================="
while [ true ]
do
/usr/bin/python2 -m toontown.uberdog.ServiceStart --base-channel $BASE_CHANNEL \
--max-channels $MAX_CHANNELS --stateserver $STATESERVER \
--astron-ip $ASTRON_IP --eventlogger-ip $EVENTLOGGER_IP
done

2
dependencies/astron/logs/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

BIN
dependencies/astron/msvcp120.dll vendored Executable file

Binary file not shown.

BIN
dependencies/astron/msvcr120.dll vendored Executable file

Binary file not shown.

32
dependencies/astron/prod/start-ai-server.sh vendored Executable file
View file

@ -0,0 +1,32 @@
#!/bin/sh
cd ../../..
# Define some constants for our AI server:
MAX_CHANNELS=999999
STATESERVER=4002
ASTRON_IP="158.69.28.83:7100"
EVENTLOGGER_IP="158.69.28.83:7198"
# Get the user input:
read -p "District name (DEFAULT: Nuttyboro): " DISTRICT_NAME
DISTRICT_NAME=${DISTRICT_NAME:-Nuttyboro}
read -p "Base channel (DEFAULT: 401000000): " BASE_CHANNEL
BASE_CHANNEL=${BASE_CHANNEL:-401000000}
echo "==============================="
echo "Starting Toontown Stride AI server..."
echo "District name: $DISTRICT_NAME"
echo "Base channel: $BASE_CHANNEL"
echo "Max channels: $MAX_CHANNELS"
echo "State Server: $STATESERVER"
echo "Astron IP: $ASTRON_IP"
echo "Event Logger IP: $EVENTLOGGER_IP"
echo "==============================="
while [ true ]
do
python -m toontown.ai.ServiceStart --base-channel $BASE_CHANNEL \
--max-channels $MAX_CHANNELS --stateserver $STATESERVER \
--astron-ip $ASTRON_IP --eventlogger-ip $EVENTLOGGER_IP \
--district-name $DISTRICT_NAME
done

View file

@ -0,0 +1,3 @@
#!/bin/sh
cd ..
./astrond --loglevel info config/prod-test.yml

View file

@ -0,0 +1,28 @@
#!/bin/sh
cd ../../..
# Define some constants for our AI server:
MAX_CHANNELS=999999
STATESERVER=4002
ASTRON_IP="158.69.28.83:7100"
EVENTLOGGER_IP="158.69.28.83:7198"
# Get the user input:
read -p "Base channel (DEFAULT: 1000000): " BASE_CHANNEL
BASE_CHANNEL=${BASE_CHANNEL:-1000000}
echo "==============================="
echo "Starting Toontown Stride UberDOG server..."
echo "Base channel: $BASE_CHANNEL"
echo "Max channels: $MAX_CHANNELS"
echo "State Server: $STATESERVER"
echo "Astron IP: $ASTRON_IP"
echo "Event Logger IP: $EVENTLOGGER_IP"
echo "==============================="
while [ true ]
do
python -m toontown.uberdog.ServiceStart --base-channel $BASE_CHANNEL \
--max-channels $MAX_CHANNELS --stateserver $STATESERVER \
--astron-ip $ASTRON_IP --eventlogger-ip $EVENTLOGGER_IP
done

BIN
dependencies/astron/ssleay32.dll vendored Executable file

Binary file not shown.

112
dependencies/config/general.prc vendored Normal file
View file

@ -0,0 +1,112 @@
# Window settings:
window-title Toontown Stride
win-origin -1 -1
icon-filename phase_3/etc/icon.ico
cursor-filename phase_3/etc/toonmono.cur
# Audio:
audio-library-name p3openal_audio
video-library-name p3ffmpeg
# Graphics:
aux-display pandagl
aux-display pandadx9
aux-display p3tinydisplay
# Models:
model-cache-models #f
model-cache-textures #f
default-model-extension .bam
# Textures:
texture-anisotropic-degree 16
# Preferences:
preferences-filename user/preferences.json
# Backups:
backups-filepath dependencies/backups/
backups-extension .json
# Server:
server-timezone EST/EDT/-5
server-port 7199
account-bridge-filename astron/databases/account-bridge.db
# Performance:
texture-power-2 none
gl-check-errors #f
garbage-collect-states #t
# Egg object types:
egg-object-type-barrier <Scalar> collide-mask { 0x01 } <Collide> { Polyset descend }
egg-object-type-trigger <Scalar> collide-mask { 0x01 } <Collide> { Polyset descend intangible }
egg-object-type-sphere <Scalar> collide-mask { 0x01 } <Collide> { Sphere descend }
egg-object-type-trigger-sphere <Scalar> collide-mask { 0x01 } <Collide> { Sphere descend intangible }
egg-object-type-floor <Scalar> collide-mask { 0x02 } <Collide> { Polyset descend }
egg-object-type-dupefloor <Scalar> collide-mask { 0x02 } <Collide> { Polyset keep descend }
egg-object-type-camera-collide <Scalar> collide-mask { 0x04 } <Collide> { Polyset descend }
egg-object-type-camera-collide-sphere <Scalar> collide-mask { 0x04 } <Collide> { Sphere descend }
egg-object-type-camera-barrier <Scalar> collide-mask { 0x05 } <Collide> { Polyset descend }
egg-object-type-camera-barrier-sphere <Scalar> collide-mask { 0x05 } <Collide> { Sphere descend }
egg-object-type-model <Model> { 1 }
egg-object-type-dcs <DCS> { 1 }
# Safe zones:
want-safe-zones #t
want-toontown-central #t
want-donalds-dock #t
want-daisys-garden #t
want-minnies-melodyland #t
want-the-brrrgh #t
want-donalds-dreamland #t
want-goofy-speedway #t
want-outdoor-zone #t
want-golf-zone #t
# Safe zone settings:
want-treasure-planners #t
want-suit-planners #t
want-butterflies #t
# Trolley minigames:
want-minigames #t
# Picnic table board games:
want-game-tables #t
# Cog headquarters:
want-cog-headquarters #t
want-sellbot-headquarters #t
want-cashbot-headquarters #t
want-lawbot-headquarters #t
want-bossbot-headquarters #t
# Cog battles:
base-xp-multiplier 1.0
# SOS toons:
sos-card-reward 2
# CogDominiums (Field Offices):
want-emblems #t
cogdo-want-barrel-room #t
want-lawbot-cogdo #t
# Cog buildings:
want-cogbuildings #t
# Optional:
show-total-population #t
want-mat-all-tailors #t
estate-day-night #t
want-garden-game #f
want-language-selection #t
# Developer options:
want-dev #f
want-pstats 0
# Temporary:
smooth-lag 0.4
want-old-fireworks #t

1
dependencies/config/guieditor.prc vendored Normal file
View file

@ -0,0 +1 @@
model-path ../resources/

64
dependencies/config/release/dev.prc vendored Normal file
View file

@ -0,0 +1,64 @@
# Distribution:
distribution dev
# Art assets:
vfs-mount resources/phase_3 /phase_3
vfs-mount resources/phase_3.5 /phase_3.5
vfs-mount resources/phase_4 /phase_4
vfs-mount resources/phase_5 /phase_5
vfs-mount resources/phase_5.5 /phase_5.5
vfs-mount resources/phase_6 /phase_6
vfs-mount resources/phase_7 /phase_7
vfs-mount resources/phase_8 /phase_8
vfs-mount resources/phase_9 /phase_9
vfs-mount resources/phase_10 /phase_10
vfs-mount resources/phase_11 /phase_11
vfs-mount resources/phase_12 /phase_12
vfs-mount resources/phase_13 /phase_13
vfs-mount resources/server /server
model-path /
# Server:
server-version tts-dev
min-access-level 700
accountdb-type developer
shard-low-pop 50
shard-mid-pop 100
# RPC:
want-rpc-server #f
rpc-server-endpoint http://localhost:8080/
# DClass file:
dc-file dependencies/astron/dclass/stride.dc
# Core features:
want-pets #t
want-parties #f
want-cogdominiums #t
want-lawbot-cogdo #t
want-anim-props #t
want-game-tables #t
want-find-four #t
want-chinese-checkers #t
want-checkers #t
want-house-types #t
want-gifting #t
want-top-toons #f
# Chat:
want-whitelist #f
want-sequence-list #f
# Developer options:
show-population #t
want-instant-parties #t
want-instant-delivery #t
cogdo-pop-factor 1.5
cogdo-ratio 0.5
default-directnotify-level info
# Crates:
dont-destroy-crate #t
get-key-reward-always #t
get-crate-reward-always #t

50
dependencies/config/release/qa.prc vendored Normal file
View file

@ -0,0 +1,50 @@
# Distribution:
distribution qa
# Art assets:
model-path /
# Server:
server-version SERVER-VERSION-HERE
min-access-level 100
accountdb-type remote
shard-low-pop 50
shard-mid-pop 100
# RPC:
want-rpc-server #t
rpc-server-endpoint http://localhost:8080/
# DClass file is automatically wrapped into the niraidata.
# Core features:
want-pets #t
want-parties #f
want-cogdominiums #t
want-lawbot-cogdo #f
want-anim-props #t
want-game-tables #t
want-find-four #t
want-chinese-checkers #t
want-checkers #t
want-house-types #f
want-gifting #t
want-top-toons #f
want-emblems #f
# Chat:
want-whitelist #t
want-sequence-list #t
# Developer options:
show-population #t
want-instant-parties #f
want-instant-delivery #f
cogdo-pop-factor 1.5
cogdo-ratio 0.5
default-directnotify-level info
# Crates:
dont-destroy-crate #f
get-key-reward-always #f
get-crate-reward-always #f

BIN
dependencies/libpandadna.pyd vendored Executable file

Binary file not shown.

BIN
dependencies/libpandadna.so vendored Executable file

Binary file not shown.

View file

@ -0,0 +1,22 @@
#!/bin/sh
cd ..
export DYLD_LIBRARY_PATH=`pwd`/Libraries.bundle
export DYLD_FRAMEWORK_PATH="Frameworks"
# Get the user input:
read -p "Username: " ttsUsername
# Export the environment variables:
export ttsUsername=$ttsUsername
export ttsPassword="password"
export TTS_PLAYCOOKIE=$ttsUsername
export TTS_GAMESERVER="127.0.0.1"
echo "==============================="
echo "Starting Toontown Stride..."
echo "Username: $ttsUsername"
echo "Gameserver: $TTS_GAMESERVER"
echo "==============================="
ppython -m toontown.toonbase.ToontownStart

View file

@ -0,0 +1,26 @@
#!/bin/sh
cd ..
export DYLD_LIBRARY_PATH=`pwd`/Libraries.bundle
export DYLD_FRAMEWORK_PATH="Frameworks"
# Get the user input:
read -p "Username: " ttsUsername
read -s -p "Password: " ttsPassword
echo
read -p "Gameserver (DEFAULT: 167.114.28.238): " TTS_GAMESERVER
TTS_GAMESERVER=${TTS_GAMESERVER:-"167.114.28.238"}
# Export the environment variables:
export ttsUsername=$ttsUsername
export ttsPassword=$ttsPassword
export TTS_PLAYCOOKIE=$ttsUsername
export TTS_GAMESERVER=$TTS_GAMESERVER
echo "==============================="
echo "Starting Toontown Stride..."
echo "Username: $ttsUsername"
echo "Gameserver: $TTS_GAMESERVER"
echo "==============================="
ppython -m toontown.toonbase.ToontownStartRemoteDB

24
dev/darwin/start-game.sh Normal file
View file

@ -0,0 +1,24 @@
#!/bin/sh
cd ..
export DYLD_LIBRARY_PATH=`pwd`/Libraries.bundle
export DYLD_FRAMEWORK_PATH="Frameworks"
# Get the user input:
read -p "Username: " ttsUsername
read -p "Gameserver (DEFAULT: 167.114.28.238): " TTS_GAMESERVER
TTS_GAMESERVER=${TTS_GAMESERVER:-"167.114.28.238"}
# Export the environment variables:
export ttsUsername=$ttsUsername
export ttsPassword="password"
export TTS_PLAYCOOKIE=$ttsUsername
export TTS_GAMESERVER=$TTS_GAMESERVER
echo "==============================="
echo "Starting Toontown Stride..."
echo "Username: $ttsUsername"
echo "Gameserver: $TTS_GAMESERVER"
echo "==============================="
ppython -m toontown.toonbase.ToontownStart

View file

@ -0,0 +1,32 @@
#!/usr/bin/env python2
# Cleanse the "../" directory of the following:
# - all files with a .pyc extension.
# - all "trash files" listed below:
# . parsetab.py - generated by the PLY module.
import os
extensions = ('.pyc',)
trashFiles = ('parsetab.py',)
print 'Changing to root directory...'
os.chdir('../../../')
print 'Scanning for garbage files...'
def delete(filepath):
print "Removing '%s'..." % filepath
os.unlink(filepath)
for root, folders, files in os.walk('.'):
for filename in files:
filepath = os.path.join(root, filename)
if os.path.splitext(filename)[1] in extensions:
delete(filepath)
elif filename in trashFiles:
delete(filepath)
print 'Done.'

View file

@ -0,0 +1,9 @@
Building
========
These documents outline everything you need to know for building a Toontown Stride client.
- - -
## Steps ##
TODO

View file

@ -0,0 +1,9 @@
Toontown Stride Style Guidelines
==================================
Code and documentation in the master and release branches of the Toontown Stride repositories must conform to these guidelines. Any code submitted that is not properly formated will be rejected, as it is best to keep a readable, and consistent style for future contributors to read, and understand the code. Don't, however, blindly follow these guidelines into writing unreadable code. Sometimes it is best to use your own judgement.
- - -
* [Git style guidelines](git-style.md)
* [Python style guidelines](python-style.md)
* [C++ style guidelines](cxx-style.md)

View file

@ -0,0 +1,145 @@
C++ Style Guidelines
====================
For C++ programming, we use the [Oct 8, 2013 - Astron C++ Style Guide](https://github.com/Astron/Astron/blob/6a974ce247a364fdcd11d440db1cad0f1c2f6ba2/doc/style-guide/cxx-style.md "Oct 8, 2013 - Astron C++ Style Guide").
- - -
## Whitespace ##
Tabs shall be used to indent, spaces shall be used to align.
Source files should end with a newline.
## Variable Names ##
Variables shall have a descriptive lowercase name, where words are seperated by underscores.
Global variables shall start with g_
Member variables shall start with m_
Example: `field_count` or
class Foo
{
private:
int m_my_number;
};
## Function Names ##
Functions shall be named the same way as variables
## Class Names ##
Class names shall be in CamelCase
Example: `DistributedObject`
## Braces ##
Each brace shall be on it's own line, even if it's for an empty member:
Example:
void foo()
{
}
## Header Files ##
A class shall not have a header file if nothing else interacts with it or if nothing else will ever inherit from it.
## Typedefs ##
Typedefs shall have a descriptive name and end with _t
Example: `typedef unsigned int uint32_t`
## Usage of std::string (and when possible for stl types) ##
Whenever possible, have function parameters be of type `const std::string&` and not `std::string`
Example:
void foo(const std::string &name)
{
std::cout << "Hello " << name << "!" << std::endl;
}
## Preprocessor Macros ##
Preproc macros shall be UPPER CASE and words shall be seperated by underlines.
Example: `#define CLIENT_HELLO 1`
## auto Keyword ##
The `auto` keyword shall be used to avoid typing out long iterator types, and only for that.
Example:
std::list<DistributedObjects> my_objects;
auto it = my_objects.begin();
## Template specifiers ##
There shall be no space between the identifier and the <>
Good example: `std::list<channel_t> my_channels;`
Bad example: `std::list <channel_t> my_channels;`
## Access specifiers ##
Class/struct access specifiers shall be indented.
Good example:
class Foo
{
public:
Foo();
};
Bad examples:
class Foo
{
public:
Foo();
};
class Foo
{
public:
Foo();
};
## Switches ##
Case statements inside switches shall be indented. If the case does not fall through, it shall have it's own scope.
The braces for the scope shall be on the same indentation level as the case statement itself.
At the end of the scope, a `break;` shall be placed on the same indentation level as the case statement.
Example:
int temp = some_int;
switch(temp)
{
case 0:
{
//code
}
break;
default:
//code
}
## Character Limit ##
Each line shall be no longer than 100 characters, each tab counting as 4 characters.
This is not a hard-limit, exceptions may be made.
## Initializer lists ##
The first variable inside of the initializer list shall be on the same line as the function header
if the character limit allows.
The following variables will be on the next line, with another level of indentation.
That line will continue until it hits the character limit, once that occurs a new line will be created,
with the same level of indenation.
Example:
class Foo
{
private:
int m_number;
int m_number2;
public:
Foo() : m_number(0),
m_number2(0)
{
}
};

View file

@ -0,0 +1,30 @@
Git Style Guidelines
====================
For Git, we try to follow a general pattern for commit messages and branch naming to make things organized and neat.
- - -
## Commit Messages ##
All commit messages should:
* Start with a capital letter.
* Never end in puncuation.
* Be in the present tense.
* Have a title less than 100 characters.
* End in a new line.
If a description is provided in the commit message, it should be separated from the title by a blank line. If the commit addresses an issue, its issue number should be referenced at the end of the commit message's description.
Whenever possible, commit messages should be prefixed with the directory name of which the commit modified the most, followed by a colon and a space.
For example: ```toon: ``` or ```tools: ``` or ```ai: ```
## Branch Naming ##
All branch names should:
* Be entirely lower case.
* Use **-** as a separator.
* Be categorized into one of the following groups:
* wip
* bugfix
* test
* enhancement
* feature
For example: ```feature/parties``` or ```bugfix/toontorial``` or ```enhancement/fix-memory-leak```

View file

@ -0,0 +1,37 @@
Python Style Guidelines
=======================
For Python programming, we use a slightly modified version of the standard [PEP-8 Style Guide for Python Code](http://legacy.python.org/dev/peps/pep-0008 "PEP-8 Style Guide for Python Code"). Read below for our modifications.
- - -
## Code lay-out ##
### Indentation ###
The closing brace/bracket/parenthesis on multi-line constructs may either be directly at the end, as in:
my_list = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20]
result = some_function_that_takes_arguments(
'a', 'b', 'c', 'd', 'e', 'f')
or it may be by itself on the next line:
my_list = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20
]
result = some_function_that_takes_arguments(
'a', 'b', 'c', 'd', 'e', 'f'
)
### Tabs or Spaces? ###
**_Always_** use spaces.
### Maximum Line Length ###
**Docstrings and comments** should be restricted to _80 characters_. Anything else should be limited to _100 characters_.
## Naming Conventions ##
### Variables ###
Intentionally **unused** variables should be named "**_**". This will make common IDEs and editors ignore it.
## Strings ##
### Quotations ###
Use single quotations _(')_ unless there is one inside the string, in which case use double quotations _(")_.

View file

@ -0,0 +1,19 @@
import glob
def processFile(f,t):
data = open(f,'rb').read()
lines = data.replace('\r\n','\n').split('\n')
lines_found = []
for i,x in enumerate(lines):
if t in x:
lines_found.append(i+1)
return lines_found
term = raw_input('> ')
for x in glob.glob('../../../toontown/*/*.py'):
r = processFile(x,term)
if r:
print x,r
raw_input('*****')

View file

@ -0,0 +1,19 @@
import glob
def processFile(f,t):
data = open(f,'rb').read()
lines = data.replace('\r\n','\n').split('\n')
lines_found = []
for i,x in enumerate(lines):
if t in x:
lines_found.append(i+1)
return lines_found
term = raw_input('> ')
for x in glob.glob('../../../toontown/*/*AI.py'):
r = processFile(x,term)
if r:
print x,r
raw_input('*****')

View file

@ -0,0 +1,19 @@
import glob
def processFile(f,t):
data = open(f,'rb').read()
lines = data.replace('\r\n','\n').split('\n')
lines_found = []
for i,x in enumerate(lines):
if t in x:
lines_found.append(i+1)
return lines_found
term = raw_input('> ')
for x in glob.glob('../../../otp/*/*.py'):
r = processFile(x,term)
if r:
print x,r
raw_input('*****')

View file

@ -0,0 +1,32 @@
from direct.stdpy import threading
from direct.showbase.ShowBase import ShowBase
from panda3d.core import VirtualFileSystem
import __builtin__, wx, os
__builtin__.__dict__.update(__import__('panda3d.core', fromlist=['*']).__dict__)
loadPrcFile('dependencies/config/guieditor.prc')
loadPrcFile('dependencies/config/general.prc')
defaultText = """from direct.gui.DirectGui import *
"""
def inject(_):
code = textbox.GetValue()
exec(code, globals())
app = wx.App(redirect=False)
frame = wx.Frame(None, title="Injector", size=(640, 400), style=wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.MINIMIZE_BOX)
panel = wx.Panel(frame)
button = wx.Button(parent=panel, id=-1, label="Inject", size=(50, 20), pos=(295, 0))
textbox = wx.TextCtrl(parent=panel, id=-1, pos=(20, 22), size=(600, 340), style=wx.TE_MULTILINE)
frame.Bind(wx.EVT_BUTTON, inject, button)
frame.Show()
app.SetTopWindow(frame)
textbox.AppendText(defaultText)
threading.Thread(target=app.MainLoop).start()
__builtin__.base = ShowBase()
base.run()

View file

@ -0,0 +1,15 @@
@echo off
cd ../../../
title GUI Editor
set GUI=
:main
"C:\Panda3D-1.10.0\python\ppython.exe" "dev/tools/gui/EditorStart.py"
echo.
echo.
goto main

View file

@ -0,0 +1,34 @@
from Crypto.Cipher import AES
from Crypto import Random
import base64
import os
# the block size for the cipher object; must be 16, 24, or 32 for AES
BLOCK_SIZE = 16
# the character used for padding--with a block cipher such as AES, the value
# you encrypt must be a multiple of BLOCK_SIZE in length. This character is
# used to ensure that your value is always a multiple of BLOCK_SIZE
PADDING = '{'
# one-liner to sufficiently pad the text to be encrypted
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
# one-liners to encrypt/encode and decrypt/decode a string
# encrypt with AES, encode with base64
EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
# generate a random secret key
secret = os.urandom(BLOCK_SIZE)
# create a cipher object using the random secret
cipher = AES.new( secret, AES.MODE_CBC, secret )
# encode a string
encoded = EncodeAES(cipher, 'ttsrpclib691256')
print 'Encrypted string:', encoded
# decode the encoded string
decoded = DecodeAES(cipher, encoded)
print 'Decrypted string:', decoded

View file

@ -0,0 +1,5 @@
@echo off
cd ../../../
set /P serv=RPC Server:
"C:\Panda3D-1.10.0\python\ppython.exe" "dev/tools/rpc/rpc-invasions.py" 6163636f756e7473 %serv%
pause

View file

@ -0,0 +1,78 @@
#!/usr/bin/python
from jsonrpclib import Server
import time
import random
import json
import math
import os
from Crypto.Cipher import AES
import base64
import sys
RPC_SERVER_SECRET = sys.argv[1]
client = Server(sys.argv[2])
def generate_token(accessLevel):
"""
Generate an RPC server token with the given access level.
"""
token = {'timestamp': int(time.mktime(time.gmtime())), 'accesslevel': accessLevel}
data = json.dumps(token)
iv = os.urandom(AES.block_size)
cipher = AES.new(RPC_SERVER_SECRET, mode=AES.MODE_CBC, IV=iv)
data += '\x00' * (16 - (len(data)%AES.block_size))
token = cipher.encrypt(data)
return base64.b64encode(iv + token)
random.seed()
while True:
try:
res = client.ping(generate_token(700), 12345)
if res != 12345:
print "Is the server accessable?\n"
exit
# How many times a day is this script going to be called?
ChecksPerDay = 60.0*24.0 # Once a minute
InvasionsPerDay = 72.0 # How many invasions a day per district
BaseInvasionChance = InvasionsPerDay/ChecksPerDay
safeHarbor = {'Wacky Falls'}
superDistricts = {'Nuttyboro'}
while True:
shards = client.listShards(generate_token(700))
print shards
count = 0
for skey in shards:
shard = shards[skey]
if shard['invasion'] != None:
count = count + 1
for skey in shards:
shard = shards[skey]
if shard['invasion'] == None:
if shard['name'] in superDistricts:
typ = int(float(random.random()) * 4.0)
suit = int(float(random.random()) * 4.0) + 4 # Bias the cogs to be big
client.startInvasion(generate_token(700), int(skey), typ, suit, 0, 0)
count = count + 1
print 'Calling invasion for %s with %d,%d'%(shard['name'],typ,suit)
if count < 3:
for skey in shards:
shard = shards[skey]
if shard['invasion'] == None and not shard['name'] in safeHarbor:
r = random.random()
if r < BaseInvasionChance and not shard['name'] in superDistricts:
typ = int(float(random.random()) * 4.0)
suit = int(float(random.random()) * 8.0)
client.startInvasion(generate_token(700), int(skey), typ, suit, 0, 0)
print 'Calling invasion for %s with %d,%d'%(shard['name'],typ,suit)
print "tick..(was %d)\n"%(count)
time.sleep(60)
except Exception, e:
print e
time.sleep(300)

5
dev/tools/rpc/rpctest.py Normal file
View file

@ -0,0 +1,5 @@
import pyjsonrpc
http_client = pyjsonrpc.HttpClient(url = 'http://localhost:8080')
print 'Connected'
print http_client.ping('test')

View file

@ -0,0 +1,223 @@
#!/usr/bin/env python2
import collections
import compiler
import re
class CategoryParser:
def __init__(self, filepath):
self.filepath = filepath
self.categories = {}
def parse(self):
with open(self.filepath, 'r') as f:
for i, line in enumerate(f.readlines()):
line = line.strip()
if (not line.startswith('# --- ')) or (not line.endswith(' ---')):
continue
self.addCategory(i + 1, line[6:-4].title())
def addCategory(self, lineno, category):
self.categories[lineno] = category
def getCategory(self, lineno):
category = 'Unknown'
for k in sorted(self.categories.keys()):
if k > lineno:
break
category = self.categories[k]
return category
class MethodParser(CategoryParser):
def __init__(self, filepath):
CategoryParser.__init__(self, filepath)
# Order matters, so store the method information in an OrderedDict:
self.methods = collections.OrderedDict()
def parse(self):
CategoryParser.parse(self)
# Get the root node:
node = compiler.parseFile(self.filepath).node
# Parse any class objects:
for child in node.getChildren():
if isinstance(child, compiler.ast.Class):
self.parseClass(child)
def parseClass(self, node):
# Skip past the class definition, and go into the body:
stmt = node.getChildNodes()[-1]
# Parse any function objects:
for child in stmt.getChildren():
if isinstance(child, compiler.ast.Function):
self.parseFunction(child)
def parseFunction(self, node):
# First, verify that this is an RPC method:
if not node.name.startswith('rpc_'):
# RPC methods are required to have their name begin with 'rpc_'.
return
name = node.name[4:]
if node.decorators is None:
# RPC methods are also required to utilize the @rpcmethod
# decorator.
return
for decorator in node.decorators:
if decorator.node.name != 'rpcmethod':
continue
accessLevel = 'Unknown'
for arg in decorator.args:
if arg.name != 'accessLevel':
continue
# Format the access level string:
accessLevel = ' '.join(arg.expr.name.split('_')).title()
break
else:
return
# A docstring is also required:
if node.doc is None:
return
# Get rid of the indentation in our docstring so that we can have an
# easier time parsing it:
lines = node.doc.split('\n')
for i, line in enumerate(lines):
lines[i] = line.lstrip()
doc = '\n'.join(lines)
# Get the category in which this method is underneath:
category = self.getCategory(node.lineno)
# Store this method's information:
self.methods.setdefault(category, []).append((name, accessLevel, doc))
def getMethods(self):
return self.methods
class MediaWikiGenerator:
def __init__(self, methods):
self.methods = methods
self.content = ''
def generate(self):
# Start on a clean slate:
self.content = ''
# Write the documentation header:
self.writeHeader()
# Write the categories and methods:
for category, methods in self.methods.items():
self.writeCategory(category)
for name, accessLevel, doc in methods:
self.writeMethod(name, accessLevel, doc)
# Write the documentation footer:
self.writeFooter()
return self.content
def writeHeader(self):
# Force a table of contents:
self.content += '__TOC__\n'
def writeCategory(self, category):
self.content += '= %s =\n' % category
def writeMethod(self, name, accessLevel, doc):
# First, add the method name and access level:
self.writeHeading(3, name + ' <sub>- <code>%s</code></sub>' % accessLevel)
# Split the docstring by the '\n\n' terminator:
doc = doc.split('\n\n')
# A summary is required, so let's assume it's first:
summary = doc[0][9:].strip()
self.writeBlockQuote(' '.join(summary.split('\n')))
# Let's do parameters next if we have them:
for entry in doc:
if entry.startswith('Parameters:'):
entry = entry[13:].strip()
break
else:
entry = None
if entry is not None:
parameters = []
for parameter in re.split('\\n\\[|\\n<', entry):
name, description = parameter.split(' = ', 1)
type, name = name.strip()[:-1].split(' ', 1)
description = ' '.join(description.split('\n'))
parameters.append((name, type, description))
self.writeParameters(parameters)
# Finally, we have an optional example response:
for entry in doc:
if entry.startswith('Example response:'):
entry = entry[18:].strip()
break
else:
entry = None
if entry is not None:
self.content += '{|\n'
self.content += '|-\n'
if (not entry.startswith('On success:')) or (
'On failure:' not in entry):
# Generate a single-row table:
self.content += '! Example Response\n'
self.content += '| <nowiki>%s</nowiki>\n' % entry
else:
# Generate a double-row table:
success, failure = entry[12:].split('On failure:', 1)
self.content += '! rowspan="2"|Example Response\n'
self.content += '| Success\n'
self.content += '| <nowiki>%s</nowiki>\n' % success.strip()
self.content += '|-\n'
self.content += '| Failure\n'
self.content += '| <nowiki>%s</nowiki>\n' % failure.strip()
self.content += '|}\n'
return self.content
def writeHeading(self, size, text):
self.content += '<h%d>%s</h%d>\n' % (size, text, size)
def writeBlockQuote(self, text):
self.content += '<blockquote><nowiki>%s</nowiki></blockquote>\n' % text
def writeParameters(self, parameters):
self.content += '{|\n'
self.content += '|-\n'
self.content += '! rowspan="%d"|Parameters\n' % (len(parameters) + 1)
self.content += '! Name\n'
self.content += '! Type\n'
self.content += '! Description\n'
for name, type, description in parameters:
self.content += '|-\n'
self.content += '| <nowiki>%s</nowiki>\n' % name
self.content += '| <nowiki>%s</nowiki>\n' % type
self.content += '| <nowiki>%s</nowiki>\n' % description
self.content += '|}\n'
def writeFooter(self):
# Let the reader know that this documentation was automatically
# generated:
self.content += '----\n'
self.content += ("''This document was automatically generated by the "
"<code>write_rpc_doc.py</code> utility.''\n")
parser = MethodParser('toontown/rpc/ToontownRPCHandler.py')
parser.parse()
generator = MediaWikiGenerator(parser.getMethods())
with open('wiki.txt', 'w') as f:
f.write(generator.generate())

View file

@ -0,0 +1,3 @@
@echo off
"C:\Panda3D-1.10.0\python\ppython.exe" -m whitelist_tool
pause

View file

@ -0,0 +1,85 @@
import os
os.chdir('../../../')
from otp.chat import WhiteListData
def acceptWord():
word = raw_input('> ').rstrip().lower()
if word == 'exit()':
saveChanges()
return
if word.startswith('r '):
word = word.replace('r ', '')
if word not in LOCAL_LIST:
print 'Could not remove unknown word "%s" from the whitelist.' % word
else:
LOCAL_LIST.remove(word)
print 'Removed "%s" from the whitelist.' % word
elif word.startswith('m '):
merge = word.replace('m ', '')
if os.path.isfile(merge):
print 'Opening %s...' % merge
with open(merge) as file:
for line in file.readlines():
line = line.replace('\r', '').replace('\n', '').lower()
print 'Adding %s...' % line
LOCAL_LIST.append(line)
else:
print 'No file named %s!' % merge
elif word in LOCAL_LIST:
print 'The word "%s" is already whitelisted.' % word
else:
LOCAL_LIST.append(word)
print 'Added the word "%s" to the whitelist.' % word
acceptWord()
def removeDuplicates(list):
seen = set()
seen_add = seen.add
return [x for x in list if not (x in seen or seen_add(x))]
def saveChanges():
global LOCAL_LIST
print 'Saving the whitelist...'
with open('otp/chat/WhiteListData.py', 'w') as f:
f.write('WHITELIST = [\n')
LOCAL_LIST.sort()
LOCAL_LIST = removeDuplicates(LOCAL_LIST)
for word in LOCAL_LIST:
if '\\' in word:
print 'Word contains illegal characters: %s' % word
continue
try:
word.decode('ascii')
except:
print 'Word cannot be decoded in ASCII mode: %s' % word
continue
if "'" in word:
f.write(' "%s",\n' % word)
else:
f.write(" '%s',\n" % word)
f.write(']')
print 'Your changes have been saved! Make sure to push your changes!'
LOCAL_LIST = WhiteListData.WHITELIST
print 'Welcome to the Toontown Stride Whitelist Tool!'
print 'Type any word you want to add to the whitelist.'
print 'If you wish to remove a word, type "r <word>".'
print 'If you wish to merge a file, type "m <file>".'
print 'When you are done and want to save your changes, type "exit()".'
acceptWord()

37
dev/win32/start-ai-server.bat Executable file
View file

@ -0,0 +1,37 @@
@echo off
rem Define some constants for our AI server:
set MAX_CHANNELS=999999
set STATESERVER=4002
set ASTRON_IP=127.0.0.1:7100
set EVENTLOGGER_IP=127.0.0.1:7198
rem Get the user input:
set /P DISTRICT_NAME="District name (DEFAULT: Nuttyboro): " || ^
set DISTRICT_NAME=Nuttyboro
set /P BASE_CHANNEL="Base channel (DEFAULT: 401000000): " || ^
set BASE_CHANNEL=401000000
echo ===============================
echo Starting Toontown Stride AI server...
echo ppython: "C:\Panda3D-1.10.0\python\ppython.exe"
echo District name: %DISTRICT_NAME%
echo Base channel: %BASE_CHANNEL%
echo Max channels: %MAX_CHANNELS%
echo State Server: %STATESERVER%
echo Astron IP: %ASTRON_IP%
echo Event Logger IP: %EVENTLOGGER_IP%
echo ===============================
cd ../../
:main
"C:\Panda3D-1.10.0\python\ppython.exe" ^
-m toontown.ai.ServiceStart ^
--base-channel %BASE_CHANNEL% ^
--max-channels %MAX_CHANNELS% ^
--stateserver %STATESERVER% ^
--astron-ip %ASTRON_IP% ^
--eventlogger-ip %EVENTLOGGER_IP% ^
--district-name "%DISTRICT_NAME%"
goto main

View file

@ -0,0 +1,5 @@
@echo off
cd "../../dependencies/astron/"
astrond --loglevel info config/cluster.yml
pause

75
dev/win32/start-game.bat Executable file
View file

@ -0,0 +1,75 @@
@echo off
title Toontown Stride Game Launcher
echo Choose your connection method!
echo.
echo #1 - Localhost
echo #2 - Dev Server
echo #3 - Custom
echo #4 - Local RemoteDB
echo #5 - Prod Server
echo.
:selection
set INPUT=-1
set /P INPUT=Selection:
if %INPUT%==1 (
set TTS_GAMESERVER=127.0.0.1
) else if %INPUT%==2 (
set TTS_GAMESERVER=167.114.220.172
) else if %INPUT%==4 (
set TTS_GAMESERVER=127.0.0.1
) else if %INPUT%==5 (
SET TTS_GAMESERVER=lw2.ez-webz.com:7198
) else if %INPUT%==3 (
echo.
set /P TTS_GAMESERVER=Gameserver:
) else (
goto selection
)
echo.
if %INPUT%==2 (
set /P ttsUsername="Username: "
set /P ttsPassword="Password: "
) else if %INPUT%==4 (
set /P ttsUsername="Username: "
set /P ttsPassword="Password: "
) else (
set /P TTS_PLAYCOOKIE=Username:
)
echo.
echo ===============================
echo Starting Toontown Stride...
echo ppython: "C:\Panda3D-1.10.0\python\ppython.exe"
if %INPUT%==2 (
echo Username: %ttsUsername%
) else if %INPUT%==4 (
echo Username: %ttsUsername%
) else (
echo Username: %TTS_PLAYCOOKIE%
)
echo Gameserver: %TTS_GAMESERVER%
echo ===============================
cd ../../
:main
if %INPUT%==2 (
"C:\Panda3D-1.10.0\python\ppython.exe" -m toontown.toonbase.ToontownStartRemoteDB
) else if %INPUT%==4 (
"C:\Panda3D-1.10.0\python\ppython.exe" -m toontown.toonbase.ToontownStartRemoteDB
) else (
"C:\Panda3D-1.10.0\python\ppython.exe" -m toontown.toonbase.ToontownStart
)
pause
goto main

View file

@ -0,0 +1,33 @@
@echo off
rem Define some constants for our UberDOG server:
set MAX_CHANNELS=999999
set STATESERVER=4002
set ASTRON_IP=127.0.0.1:7100
set EVENTLOGGER_IP=127.0.0.1:7198
rem Get the user input:
set /P BASE_CHANNEL="Base channel (DEFAULT: 1000000): " || ^
set BASE_CHANNEL=1000000
echo ===============================
echo Starting Toontown Stride UberDOG server...
echo ppython: "C:\Panda3D-1.10.0\python\ppython.exe"
echo Base channel: %BASE_CHANNEL%
echo Max channels: %MAX_CHANNELS%
echo State Server: %STATESERVER%
echo Astron IP: %ASTRON_IP%
echo Event Logger IP: %EVENTLOGGER_IP%
echo ===============================
cd ../../
:main
"C:\Panda3D-1.10.0\python\ppython.exe" ^
-m toontown.uberdog.ServiceStart ^
--base-channel %BASE_CHANNEL% ^
--max-channels %MAX_CHANNELS% ^
--stateserver %STATESERVER% ^
--astron-ip %ASTRON_IP% ^
--eventlogger-ip %EVENTLOGGER_IP%
goto main

0
otp/__init__.py Executable file
View file

133
otp/ai/AIBase.py Executable file
View file

@ -0,0 +1,133 @@
import gc
import math
import sys
import time
from direct.directnotify.DirectNotifyGlobal import *
from direct.interval.IntervalManager import ivalMgr
from direct.showbase import EventManager
from direct.showbase import PythonUtil
from direct.showbase.BulletinBoardGlobal import *
from direct.showbase.EventManagerGlobal import *
from direct.showbase.JobManagerGlobal import *
from direct.showbase.MessengerGlobal import *
from direct.showbase.PythonUtil import *
from direct.task import Task
from direct.task.TaskManagerGlobal import *
from otp.otpbase import BackupManager
from panda3d.core import *
class AIBase:
notify = directNotify.newCategory('AIBase')
def __init__(self):
self.config = getConfigShowbase()
__builtins__['__dev__'] = self.config.GetBool('want-dev', 0)
if self.config.GetBool('use-vfs', 1):
vfs = VirtualFileSystem.getGlobalPtr()
else:
vfs = None
self.wantTk = self.config.GetBool('want-tk', 0)
self.AISleep = self.config.GetFloat('ai-sleep', 0.04)
self.AIRunningNetYield = self.config.GetBool('ai-running-net-yield', 0)
self.AIForceSleep = self.config.GetBool('ai-force-sleep', 0)
self.eventMgr = eventMgr
self.messenger = messenger
self.bboard = bulletinBoard
self.taskMgr = taskMgr
Task.TaskManager.taskTimerVerbose = self.config.GetBool('task-timer-verbose', 0)
Task.TaskManager.extendedExceptions = self.config.GetBool('extended-exceptions', 0)
self.sfxManagerList = None
self.musicManager = None
self.jobMgr = jobMgr
self.hidden = NodePath('hidden')
self.graphicsEngine = GraphicsEngine()
globalClock = ClockObject.getGlobalClock()
self.trueClock = TrueClock.getGlobalPtr()
globalClock.setRealTime(self.trueClock.getShortTime())
globalClock.setAverageFrameRateInterval(30.0)
globalClock.tick()
taskMgr.globalClock = globalClock
__builtins__['ostream'] = Notify.out()
__builtins__['globalClock'] = globalClock
__builtins__['vfs'] = vfs
__builtins__['hidden'] = self.hidden
PythonUtil.recordFunctorCreationStacks()
self.wantStats = self.config.GetBool('want-pstats', 0)
Task.TaskManager.pStatsTasks = self.config.GetBool('pstats-tasks', 0)
taskMgr.resumeFunc = PStatClient.resumeAfterPause
wantFakeTextures = self.config.GetBool('want-fake-textures-ai', 1)
if wantFakeTextures:
loadPrcFileData('aibase', 'textures-header-only 1')
self.wantPets = self.config.GetBool('want-pets', 1)
if self.wantPets:
from toontown.pets import PetConstants
self.petMoodTimescale = self.config.GetFloat('pet-mood-timescale', 1.0)
self.petMoodDriftPeriod = self.config.GetFloat('pet-mood-drift-period', PetConstants.MoodDriftPeriod)
self.petThinkPeriod = self.config.GetFloat('pet-think-period', PetConstants.ThinkPeriod)
self.petMovePeriod = self.config.GetFloat('pet-move-period', PetConstants.MovePeriod)
self.petPosBroadcastPeriod = self.config.GetFloat('pet-pos-broadcast-period', PetConstants.PosBroadcastPeriod)
self.wantBingo = self.config.GetBool('want-fish-bingo', 1)
self.wantKarts = self.config.GetBool('want-karts', 1)
self.backups = BackupManager.BackupManager(
filepath=self.config.GetString('backups-filepath', 'dependencies/backups/'),
extension=self.config.GetString('backups-extension', '.json'))
self.createStats()
self.restart()
def createStats(self, hostname = None, port = None):
if not self.wantStats:
return False
if PStatClient.isConnected():
PStatClient.disconnect()
if hostname is None:
hostname = ''
if port is None:
port = -1
PStatClient.connect(hostname, port)
return PStatClient.isConnected()
def __sleepCycleTask(self, task):
time.sleep(self.AISleep)
return Task.cont
def __resetPrevTransform(self, state):
PandaNode.resetAllPrevTransform()
return Task.cont
def __ivalLoop(self, state):
ivalMgr.step()
return Task.cont
def __igLoop(self, state):
self.graphicsEngine.renderFrame()
return Task.cont
def shutdown(self):
self.taskMgr.remove('ivalLoop')
self.taskMgr.remove('igLoop')
self.taskMgr.remove('aiSleep')
self.eventMgr.shutdown()
def restart(self):
self.shutdown()
self.taskMgr.add(self.__resetPrevTransform, 'resetPrevTransform', priority=-51)
self.taskMgr.add(self.__ivalLoop, 'ivalLoop', priority=20)
self.taskMgr.add(self.__igLoop, 'igLoop', priority=50)
if self.config.GetBool('garbage-collect-states', 1):
self.taskMgr.add(self.__garbageCollectStates, 'garbageCollectStates', priority=46)
if self.AISleep >= 0 and (not self.AIRunningNetYield or self.AIForceSleep):
self.taskMgr.add(self.__sleepCycleTask, 'aiSleep', priority=55)
self.eventMgr.restart()
def __garbageCollectStates(self, state):
TransformState.garbageCollect()
RenderState.garbageCollect()
return Task.cont
def getRepository(self):
return self.air
def run(self):
self.taskMgr.run()

32
otp/ai/AIBaseGlobal.py Executable file
View file

@ -0,0 +1,32 @@
from AIBase import *
__builtins__['simbase'] = AIBase()
__builtins__['ostream'] = Notify.out()
__builtins__['run'] = simbase.run
__builtins__['taskMgr'] = simbase.taskMgr
__builtins__['jobMgr'] = simbase.jobMgr
__builtins__['eventMgr'] = simbase.eventMgr
__builtins__['messenger'] = simbase.messenger
__builtins__['bboard'] = simbase.bboard
__builtins__['config'] = simbase.config
__builtins__['directNotify'] = directNotify
from direct.showbase import Loader
simbase.loader = Loader.Loader(simbase)
__builtins__['loader'] = simbase.loader
directNotify.setDconfigLevels()
def inspect(anObject):
from direct.tkpanels import Inspector
Inspector.inspect(anObject)
__builtins__['inspect'] = inspect
taskMgr.finalInit()
# The VirtualFileSystem, which has already initialized, doesn't see the mount
# directives in the config(s) yet. We have to force it to load those manually:
from panda3d.core import VirtualFileSystem, ConfigVariableList, Filename
vfs = VirtualFileSystem.getGlobalPtr()
mounts = ConfigVariableList('vfs-mount')
for mount in mounts:
mountfile, mountpoint = (mount.split(' ', 2) + [None, None, None])[:2]
vfs.mount(Filename(mountfile), Filename(mountpoint), 0)

209
otp/ai/AIZoneData.py Executable file
View file

@ -0,0 +1,209 @@
from panda3d.core import *
from direct.distributed import ParentMgr
from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.task import Task
from otp.otpbase import OTPGlobals
import random
class AIZoneData:
notify = directNotify.newCategory('AIZoneData')
def __init__(self, air, parentId, zoneId):
self._air = air
self._parentId = parentId
self._zoneId = zoneId
self._data = self._air.getZoneDataStore().getDataForZone(self._parentId, self._zoneId)
def destroy(self):
del self._data
self._air.getZoneDataStore().releaseDataForZone(self._parentId, self._zoneId)
del self._zoneId
del self._parentId
del self._air
def __getattr__(self, attr):
return getattr(self._data, attr)
class AIZoneDataObj:
notify = directNotify.newCategory('AIZoneDataObj')
DefaultCTravName = 'default'
def __init__(self, parentId, zoneId):
self._parentId = parentId
self._zoneId = zoneId
self._refCount = 0
self._collTravs = {}
self._collTravsStarted = set()
def __str__(self):
output = str(self._collTravs)
output += '\n'
totalColliders = 0
totalTraversers = 0
for currCollTrav in self._collTravs.values():
totalTraversers += 1
totalColliders += currCollTrav.getNumColliders()
output += 'Num traversers: %s Num total colliders: %s' % (totalTraversers, totalColliders)
return output
def _incRefCount(self):
self._refCount += 1
def _decRefCount(self):
self._refCount -= 1
def _getRefCount(self):
return self._refCount
def destroy(self):
for name in list(self._collTravsStarted):
self.stopCollTrav(cTravName=name)
del self._collTravsStarted
del self._collTravs
if hasattr(self, '_nonCollidableParent'):
self._nonCollidableParent.removeNode()
del self._nonCollidableParent
if hasattr(self, '_render'):
self._render.removeNode()
del self._render
if hasattr(self, '_parentMgr'):
self._parentMgr.destroy()
del self._parentMgr
del self._zoneId
del self._parentId
def getLocation(self):
return (self._parentId, self._zoneId)
def getRender(self):
if not hasattr(self, '_render'):
self._render = NodePath('render-%s-%s' % (self._parentId, self._zoneId))
return self._render
def getNonCollidableParent(self):
if not hasattr(self, '_nonCollidableParent'):
render = self.getRender()
self._nonCollidableParent = render.attachNewNode('nonCollidables')
return self._nonCollidableParent
def getParentMgr(self):
if not hasattr(self, '_parentMgr'):
self._parentMgr = ParentMgr.ParentMgr()
self._parentMgr.registerParent(OTPGlobals.SPHidden, hidden)
self._parentMgr.registerParent(OTPGlobals.SPRender, self.getRender())
return self._parentMgr
def hasCollTrav(self, name = None):
if name is None:
name = AIZoneDataObj.DefaultCTravName
return name in self._collTravs
def getCollTrav(self, name = None):
if name is None:
name = AIZoneDataObj.DefaultCTravName
if name not in self._collTravs:
self._collTravs[name] = CollisionTraverser('cTrav-%s-%s-%s' % (name, self._parentId, self._zoneId))
return self._collTravs[name]
def removeCollTrav(self, name):
if name in self._collTravs:
del self._collTravs[name]
def _getCTravTaskName(self, name = None):
if name is None:
name = AIZoneDataObj.DefaultCTravName
return 'collTrav-%s-%s-%s' % (name, self._parentId, self._zoneId)
def _doCollisions(self, task = None, topNode = None, cTravName = None):
render = self.getRender()
curTime = globalClock.getFrameTime()
render.setTag('lastTraverseTime', str(curTime))
if topNode is not None:
if not render.isAncestorOf(topNode):
self.notify.warning('invalid topNode for collision traversal in %s: %s' % (self.getLocation(), topNode))
else:
topNode = render
if cTravName is None:
cTravName = AIZoneDataObj.DefaultCTravName
collTrav = self._collTravs[cTravName]
messenger.send('preColl-' + collTrav.getName())
collTrav.traverse(topNode)
messenger.send('postColl-' + collTrav.getName())
return Task.cont
def doCollTrav(self, topNode = None, cTravName = None):
self.getCollTrav(cTravName)
self._doCollisions(topNode=topNode, cTravName=cTravName)
def startCollTrav(self, respectPrevTransform = 1, cTravName = None):
if cTravName is None:
cTravName = AIZoneDataObj.DefaultCTravName
if cTravName not in self._collTravsStarted:
self.getCollTrav(name=cTravName)
taskMgr.add(self._doCollisions, self._getCTravTaskName(name=cTravName), priority=OTPGlobals.AICollisionPriority, extraArgs=[self._zoneId])
self._collTravsStarted.add(cTravName)
self.setRespectPrevTransform(respectPrevTransform, cTravName=cTravName)
return
def stopCollTrav(self, cTravName = None):
if cTravName is None:
cTravName = AIZoneDataObj.DefaultCTravName
self.notify.debug('stopCollTrav(%s, %s, %s)' % (cTravName, self._parentId, self._zoneId))
if cTravName in self._collTravsStarted:
self.notify.info('removing %s collision traversal for (%s, %s)' % (cTravName, self._parentId, self._zoneId))
taskMgr.remove(self._getCTravTaskName(name=cTravName))
self._collTravsStarted.remove(cTravName)
return
def setRespectPrevTransform(self, flag, cTravName = None):
if cTravName is None:
cTravName = AIZoneDataObj.DefaultCTravName
self._collTravs[cTravName].setRespectPrevTransform(flag)
return
def getRespectPrevTransform(self, cTravName = None):
if cTravName is None:
cTravName = AIZoneDataObj.DefaultCTravName
return self._collTravs[cTravName].getRespectPrevTransform()
class AIZoneDataStore:
notify = directNotify.newCategory('AIZoneDataStore')
def __init__(self):
self._zone2data = {}
def destroy(self):
for zone, data in self._zone2data.items():
data.destroy()
del self._zone2data
def hasDataForZone(self, parentId, zoneId):
key = (parentId, zoneId)
return key in self._zone2data
def getDataForZone(self, parentId, zoneId):
key = (parentId, zoneId)
if key not in self._zone2data:
self._zone2data[key] = AIZoneDataObj(parentId, zoneId)
self.printStats()
data = self._zone2data[key]
data._incRefCount()
return data
def releaseDataForZone(self, parentId, zoneId):
key = (parentId, zoneId)
data = self._zone2data[key]
data._decRefCount()
refCount = data._getRefCount()
if refCount == 0:
del self._zone2data[key]
data.destroy()
self.printStats()
def printStats(self):
self.notify.debug('%s zones have zone data allocated' % len(self._zone2data))

142
otp/ai/BanManagerAI.py Executable file
View file

@ -0,0 +1,142 @@
from direct.directnotify import DirectNotifyGlobal
from toontown.uberdog.ClientServicesManagerUD import executeHttpRequest
import time
from direct.fsm.FSM import FSM
from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.MsgTypes import *
from otp.ai.MagicWordGlobal import *
from direct.showbase.DirectObject import DirectObject
class BanFSM(FSM):
def __init__(self, air, avId, comment, duration, banner):
FSM.__init__(self, 'banFSM-%s' % avId)
self.air = air
self.avId = avId
# Needed variables for the actual banning.
self.comment = comment
self.duration = duration
self.DISLid = None
self.accountId = None
self.avName = None
self.banner = banner
def performBan(self, duration):
executeHttpRequest('ban', username=self.accountId, start=int(time.time()),
duration=duration, reason=self.comment, bannedby=self.banner)
def ejectPlayer(self):
av = self.air.doId2do.get(self.avId)
if not av:
return
# Send the client a 'CLIENTAGENT_EJECT' with the players name.
datagram = PyDatagram()
datagram.addServerHeader(
av.GetPuppetConnectionChannel(self.avId),
self.air.ourChannel, CLIENTAGENT_EJECT)
datagram.addUint16(152)
datagram.addString(self.avName)
simbase.air.send(datagram)
def dbCallback(self, dclass, fields):
if dclass != simbase.air.dclassesByName['AccountAI']:
return
self.accountId = fields.get('ACCOUNT_ID')
if not self.accountId:
return
if simbase.config.GetBool('want-bans', True):
self.performBan(self.duration)
self.duration = None
def getAvatarDetails(self):
av = self.air.doId2do.get(self.avId)
if not av:
return
self.DISLid = av.getDISLid()
self.avName = av.getName()
def log(self):
simbase.air.writeServerEvent('ban', self.accountId, self.comment)
def cleanup(self):
self.air = None
self.avId = None
self.DISLid = None
self.avName = None
self.accountId = None
self.comment = None
self.duration = None
self = None
def enterStart(self):
self.getAvatarDetails()
self.air.dbInterface.queryObject(self.air.dbId, self.DISLid,
self.dbCallback)
self.ejectPlayer()
def exitStart(self):
self.log()
self.cleanup()
def enterOff(self):
pass
def exitOff(self):
pass
class BanManagerAI(DirectObject):
notify = DirectNotifyGlobal.directNotify.newCategory('BanManagerAI')
def __init__(self, air):
self.air = air
self.banFSMs = {}
def ban(self, avId, duration, comment, banner):
self.banFSMs[avId] = BanFSM(self.air, avId, comment, duration, banner)
self.banFSMs[avId].request('Start')
self.acceptOnce(self.air.getAvatarExitEvent(avId), self.banDone, [avId])
def banDone(self, avId):
self.banFSMs[avId].request('Off')
self.banFSMs[avId] = None
@magicWord(category=CATEGORY_MODERATOR, types=[str])
def kick(reason='No reason specified'):
"""
Kick the target from the game server.
"""
target = spellbook.getTarget()
datagram = PyDatagram()
datagram.addServerHeader(
target.GetPuppetConnectionChannel(target.doId),
simbase.air.ourChannel, CLIENTAGENT_EJECT)
datagram.addUint16(155)
datagram.addString('You were kicked by a moderator for the following reason: %s' % reason)
simbase.air.send(datagram)
return "Kicked %s from the game server!" % target.getName()
@magicWord(category=CATEGORY_MODERATOR, types=[str, int])
def ban(reason, duration):
"""
Ban the target from the game server.
"""
target = spellbook.getTarget()
if target == spellbook.getInvoker():
return "You can't ban yourself!"
if reason not in ('hacking', 'language', 'other'):
return "'%s' is not a valid reason." % reason
banner = spellbook.getInvoker().DISLid
simbase.air.banManager.ban(target.doId, duration, reason, banner)
return "Banned %s from the game server!" % target.getName()

83
otp/ai/Barrier.py Executable file
View file

@ -0,0 +1,83 @@
from otp.ai.AIBase import *
from direct.task import Task
from direct.showbase import DirectObject
import random
class Barrier(DirectObject.DirectObject):
notify = directNotify.newCategory('Barrier')
def __init__(self, name, uniqueName, avIdList, timeout, clearedFunc = None, timeoutFunc = None, doneFunc = None):
self.name = name
self.uniqueName = uniqueName + '-Barrier'
self.avIdList = avIdList[:]
self.pendingAvatars = self.avIdList[:]
self.timeout = timeout
self.clearedFunc = clearedFunc
self.timeoutFunc = timeoutFunc
self.doneFunc = doneFunc
if len(self.pendingAvatars) == 0:
self.notify.debug('%s: barrier with empty list' % self.uniqueName)
self.active = 0
if self.clearedFunc:
self.clearedFunc()
if self.doneFunc:
self.doneFunc(self.avIdList)
return
self.taskName = self.uniqueName + '-Timeout'
origTaskName = self.taskName
while taskMgr.hasTaskNamed(self.taskName):
self.taskName = origTaskName + '-' + str(random.randint(0, 10000))
taskMgr.doMethodLater(self.timeout, self.__timerExpired, self.taskName)
for avId in self.avIdList:
event = simbase.air.getAvatarExitEvent(avId)
self.acceptOnce(event, self.__handleUnexpectedExit, extraArgs=[avId])
self.notify.debug('%s: expecting responses from %s within %s seconds' % (self.uniqueName, self.avIdList, self.timeout))
self.active = 1
def cleanup(self):
if self.active:
taskMgr.remove(self.taskName)
self.active = 0
self.ignoreAll()
def clear(self, avId):
if avId not in self.pendingAvatars:
self.notify.warning('%s: tried to clear %s, who was not listed.' % (self.uniqueName, avId))
return
self.notify.debug('%s: clearing avatar %s' % (self.uniqueName, avId))
self.pendingAvatars.remove(avId)
if len(self.pendingAvatars) == 0:
self.notify.debug('%s: barrier cleared by %s' % (self.uniqueName, self.avIdList))
self.cleanup()
if self.clearedFunc:
self.clearedFunc()
if self.doneFunc:
self.doneFunc(self.avIdList)
def isActive(self):
return self.active
def getPendingAvatars(self):
return self.pendingAvatars[:]
def __timerExpired(self, task):
self.notify.warning('%s: timeout expired; responses not received from %s' % (self.uniqueName, self.pendingAvatars))
self.cleanup()
if self.timeoutFunc:
self.timeoutFunc(self.pendingAvatars[:])
if self.doneFunc:
clearedAvIds = self.avIdList[:]
for avId in self.pendingAvatars:
clearedAvIds.remove(avId)
self.doneFunc(clearedAvIds)
return Task.done
def __handleUnexpectedExit(self, avId):
if avId not in self.avIdList:
return
self.avIdList.remove(avId)
if avId in self.pendingAvatars:
self.clear(avId)

156
otp/ai/MagicWordGlobal.py Executable file
View file

@ -0,0 +1,156 @@
class MagicError(Exception): pass
def ensureAccess(access, msg='Insufficient access'):
if spellbook.getInvokerAccess() < access:
raise MagicError(msg)
class Spellbook:
"""
The Spellbook manages the list of all Magic Words that have been registered
anywhere in the system. When the MagicWordManager(AI) wants to process a
Magic Word, it is passed off to the Spellbook, which performs the operation.
To add Magic Words to the Spellbook, use the @magicWord() decorator.
"""
def __init__(self):
self.words = {}
self.currentInvoker = None
self.currentTarget = None
def addWord(self, word):
self.words[word.name.lower()] = word # lets make this stuff case insensitive
def process(self, invoker, target, incantation):
self.currentInvoker = invoker
self.currentTarget = target
word, args = (incantation.split(' ', 1) + [''])[:2]
try:
return self.doWord(word, args)
except MagicError as e:
return e.message
except Exception:
return describeException(backTrace=1)
finally:
self.currentInvoker = None
self.currentTarget = None
def doWord(self, wordName, args):
word = self.words.get(wordName.lower()) # look it up by its lower case value
if not word:
if process == 'ai':
wname = wordName.lower()
for key in self.words:
if self.words.get(key).access <= self.getInvokerAccess():
if wname in key:
return 'Did you mean %s' % (self.words.get(key).name)
if not word:
return
ensureAccess(word.access)
if self.getTarget() and self.getTarget() != self.getInvoker():
if self.getInvokerAccess() <= self.getTarget().getAdminAccess():
raise MagicError('Target must have lower access')
result = word.run(args)
if result is not None:
return str(result)
def getInvoker(self):
return self.currentInvoker
def getTarget(self):
return self.currentTarget
def getInvokerAccess(self):
if not self.currentInvoker:
return 0
return self.currentInvoker.getAdminAccess()
spellbook = Spellbook()
# CATEGORIES
class MagicWordCategory:
def __init__(self, name, defaultAccess=600):
self.name = name
self.defaultAccess = defaultAccess
CATEGORY_UNKNOWN = MagicWordCategory('Unknown')
CATEGORY_USER = MagicWordCategory('Community manager', defaultAccess=100)
CATEGORY_COMMUNITY_MANAGER = MagicWordCategory('Community manager', defaultAccess=200)
CATEGORY_MODERATOR = MagicWordCategory('Moderator', defaultAccess=300)
CATEGORY_CREATIVE = MagicWordCategory('Creative', defaultAccess=400)
CATEGORY_PROGRAMMER = MagicWordCategory('Programmer', defaultAccess=500)
CATEGORY_ADMINISTRATOR = MagicWordCategory('Administrator', defaultAccess=600)
CATEGORY_SYSTEM_ADMINISTRATOR = MagicWordCategory('System administrator', defaultAccess=700)
MINIMUM_MAGICWORD_ACCESS = CATEGORY_COMMUNITY_MANAGER.defaultAccess
class MagicWord:
def __init__(self, name, func, types, access, doc):
self.name = name
self.func = func
self.types = types
self.access = access
self.doc = doc
def parseArgs(self, string):
maxArgs = self.func.func_code.co_argcount
minArgs = maxArgs - (len(self.func.func_defaults) if self.func.func_defaults else 0)
args = string.split(None, maxArgs-1)[:maxArgs]
if len(args) < minArgs:
raise MagicError('Magic word %s requires at least %d arguments' % (self.name, minArgs))
output = []
for i, (type, arg) in enumerate(zip(self.types, args)):
try:
targ = type(arg)
except (TypeError, ValueError):
raise MagicError('Argument %d of magic word %s must be %s' % (i, self.name, type.__name__))
output.append(targ)
return output
def run(self, rawArgs):
args = self.parseArgs(rawArgs)
return self.func(*args)
class MagicWordDecorator:
"""
This class manages Magic Word decoration. It is aliased as magicWord, so that
the @magicWord(...) construct instantiates this class and has the resulting
object process the Magic Word's construction.
"""
def __init__(self, name=None, types=[str], access=None, category=CATEGORY_UNKNOWN):
self.name = name
self.types = types
self.category = category
if access is not None:
self.access = access
else:
self.access = self.category.defaultAccess
def __call__(self, mw):
# This is the actual decoration routine. We add the function 'mw' as a
# Magic Word to the Spellbook, using the attributes specified at construction
# time.
name = self.name
if name is None:
name = mw.func_name
word = MagicWord(name, mw, self.types, self.access, mw.__doc__)
spellbook.addWord(word)
return mw
magicWord = MagicWordDecorator

43
otp/ai/MagicWordManager.py Executable file
View file

@ -0,0 +1,43 @@
from direct.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedObject
from otp.ai.MagicWordGlobal import *
lastClickedNametag = None
class MagicWordManager(DistributedObject.DistributedObject):
notify = DirectNotifyGlobal.directNotify.newCategory('MagicWordManager')
neverDisable = 1
def generate(self):
DistributedObject.DistributedObject.generate(self)
self.accept('magicWord', self.handleMagicWord)
def disable(self):
self.ignore('magicWord')
DistributedObject.DistributedObject.disable(self)
def handleMagicWord(self, magicWord):
if not base.localAvatar.isAdmin():
return
if magicWord.startswith('~~'):
if lastClickedNametag == None:
target = base.localAvatar
else:
target = lastClickedNametag
magicWord = magicWord[2:]
if magicWord.startswith('~'):
target = base.localAvatar
magicWord = magicWord[1:]
targetId = target.doId
self.sendUpdate('sendMagicWord', [magicWord, targetId])
if target == base.localAvatar:
response = spellbook.process(base.localAvatar, target, magicWord)
if response:
self.sendMagicWordResponse(response)
def sendMagicWordResponse(self, response):
self.notify.info(response)
base.localAvatar.setSystemMessage(0, 'Spellbook: ' + str(response))

75
otp/ai/MagicWordManagerAI.py Executable file
View file

@ -0,0 +1,75 @@
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI
from otp.ai.MagicWordGlobal import *
from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.MsgTypes import *
class MagicWordManagerAI(DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory("MagicWordManagerAI")
def sendMagicWord(self, word, targetId):
invokerId = self.air.getAvatarIdFromSender()
invoker = self.air.doId2do.get(invokerId)
if not 'DistributedToonAI' in str(self.air.doId2do.get(targetId)):
self.sendUpdateToAvatarId(invokerId, 'sendMagicWordResponse', ['Target is not a toon object!'])
return
if not invoker:
self.sendUpdateToAvatarId(invokerId, 'sendMagicWordResponse', ['missing invoker'])
return
if not invoker.isAdmin():
self.air.writeServerEvent('suspicious', invokerId, 'Attempted to issue magic word: %s' % word)
dg = PyDatagram()
dg.addServerHeader(self.GetPuppetConnectionChannel(invokerId), self.air.ourChannel, CLIENTAGENT_EJECT)
dg.addUint16(102)
dg.addString('Magic Words are reserved for administrators only!')
self.air.send(dg)
return
target = self.air.doId2do.get(targetId)
if not target:
self.sendUpdateToAvatarId(invokerId, 'sendMagicWordResponse', ['missing target'])
return
response = spellbook.process(invoker, target, word)
if response:
self.sendUpdateToAvatarId(invokerId, 'sendMagicWordResponse', [response])
self.air.writeServerEvent('magic-word',
invokerId, invoker.getAdminAccess(),
targetId, target.getAdminAccess(),
word, response)
@magicWord(category=CATEGORY_COMMUNITY_MANAGER, types=[str])
def help(wordName=None):
if not wordName:
return "What were you interested getting help for?"
word = spellbook.words.get(wordName.lower())
if not word:
accessLevel = spellbook.getInvoker().getAdminAccess()
wname = wordName.lower()
for key in spellbook.words:
if spellbook.words.get(key).access <= accessLevel:
if wname in key:
return 'Did you mean %s' % (spellbook.words.get(key).name)
return 'I have no clue what %s is referring to' % (wordName)
return word.doc
@magicWord(category=CATEGORY_COMMUNITY_MANAGER, types=[])
def words():
accessLevel = spellbook.getInvoker().getAdminAccess()
wordString = None
for key in spellbook.words:
word = spellbook.words.get(key)
if word.access <= accessLevel:
if wordString is None:
wordString = key
else:
wordString += ", ";
wordString += key;
if wordString is None:
return "You are chopped liver"
else:
return wordString

127
otp/ai/TimeManager.py Executable file
View file

@ -0,0 +1,127 @@
from direct.directnotify import DirectNotifyGlobal
from direct.distributed import DistributedObject
from direct.distributed.ClockDelta import *
from direct.showbase import PythonUtil
from direct.showbase.DirectObject import *
from direct.task import Task
from panda3d.core import *
from otp.otpbase import OTPGlobals
import time
class TimeManager(DistributedObject.DistributedObject):
notify = DirectNotifyGlobal.directNotify.newCategory('TimeManager')
neverDisable = 1
def __init__(self, cr):
DistributedObject.DistributedObject.__init__(self, cr)
self.updateFreq = base.config.GetFloat('time-manager-freq', 1800)
self.minWait = base.config.GetFloat('time-manager-min-wait', 10)
self.maxUncertainty = base.config.GetFloat('time-manager-max-uncertainty', 1)
self.maxAttempts = base.config.GetInt('time-manager-max-attempts', 5)
self.thisContext = -1
self.nextContext = 0
self.attemptCount = 0
self.start = 0
self.lastAttempt = -self.minWait * 2
def generate(self):
self._gotFirstTimeSync = False
if self.cr.timeManager != None:
self.cr.timeManager.delete()
self.cr.timeManager = self
DistributedObject.DistributedObject.generate(self)
self.accept('clock_error', self.handleClockError)
if self.updateFreq > 0:
self.startTask()
self.setDisconnectReason(OTPGlobals.DisconnectNone)
return
def announceGenerate(self):
DistributedObject.DistributedObject.announceGenerate(self)
self.synchronize('TimeManager.announceGenerate')
def gotInitialTimeSync(self):
return self._gotFirstTimeSync
def disable(self):
self.ignore('clock_error')
self.stopTask()
if self.cr.timeManager == self:
self.cr.timeManager = None
del self._gotFirstTimeSync
DistributedObject.DistributedObject.disable(self)
return
def delete(self):
self.ignore('clock_error')
self.stopTask()
if self.cr.timeManager == self:
self.cr.timeManager = None
DistributedObject.DistributedObject.delete(self)
return
def startTask(self):
self.stopTask()
taskMgr.doMethodLater(self.updateFreq, self.doUpdate, 'timeMgrTask')
def stopTask(self):
taskMgr.remove('timeMgrTask')
def doUpdate(self, task):
self.synchronize('timer')
taskMgr.doMethodLater(self.updateFreq, self.doUpdate, 'timeMgrTask')
return Task.done
def handleClockError(self):
self.synchronize('clock error')
def synchronize(self, description):
now = globalClock.getRealTime()
if now - self.lastAttempt < self.minWait:
self.notify.debug('Not resyncing (too soon): %s' % description)
return 0
self.thisContext = self.nextContext
self.attemptCount = 0
self.nextContext = self.nextContext + 1 & 255
self.notify.info('Clock sync: %s' % description)
self.start = now
self.lastAttempt = now
self.sendUpdate('requestServerTime', [self.thisContext])
return 1
def serverTime(self, context, timestamp, timeOfDay):
end = globalClock.getRealTime()
aiTimeSkew = timeOfDay - self.cr.getServerTimeOfDay()
if context != self.thisContext:
self.notify.info('Ignoring TimeManager response for old context %d' % context)
return
elapsed = end - self.start
self.attemptCount += 1
self.notify.info('Clock sync roundtrip took %0.3f ms' % (elapsed * 1000.0))
self.notify.info('AI time delta is %s from server delta' % PythonUtil.formatElapsedSeconds(aiTimeSkew))
average = (self.start + end) / 2.0
uncertainty = (end - self.start) / 2.0
globalClockDelta.resynchronize(average, timestamp, uncertainty)
self.notify.info('Local clock uncertainty +/- %.3f s' % globalClockDelta.getUncertainty())
if globalClockDelta.getUncertainty() > self.maxUncertainty:
if self.attemptCount < self.maxAttempts:
self.notify.info('Uncertainty is too high, trying again.')
self.start = globalClock.getRealTime()
self.sendUpdate('requestServerTime', [self.thisContext])
return
self.notify.info('Giving up on uncertainty requirement.')
self._gotFirstTimeSync = True
messenger.send('gotTimeSync')
toontownTimeManager = getattr(base.cr, 'toontownTimeManager', None)
if toontownTimeManager:
toontownTimeManager.updateLoginTimes(timeOfDay, int(time.time()), globalClock.getRealTime())
def setDisconnectReason(self, disconnectCode):
self.sendUpdate('setDisconnectReason', [disconnectCode])
def setExceptionInfo(self):
info = describeException()
self.notify.info('Client exception: %s' % info)
self.sendUpdate('setExceptionInfo', [info])
self.cr.flush()

30
otp/ai/TimeManagerAI.py Executable file
View file

@ -0,0 +1,30 @@
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectAI import DistributedObjectAI
from direct.distributed.ClockDelta import globalClockDelta
from otp.otpbase import OTPGlobals
import time
class TimeManagerAI(DistributedObjectAI):
notify = DirectNotifyGlobal.directNotify.newCategory("TimeManagerAI")
def __init__(self, air):
DistributedObjectAI.__init__(self, air)
self.avId2DcReason = {}
def requestServerTime(self, context):
self.sendUpdateToAvatarId(self.air.getAvatarIdFromSender(), 'serverTime', [context, globalClockDelta.getRealNetworkTime(bits=32), int(time.time())])
def setDisconnectReason(self, reason):
avId = self.air.getAvatarIdFromSender()
if reason == OTPGlobals.DisconnectNone and avId in self.avId2DcReason:
del self.avId2DcReason[avId]
else:
self.avId2DcReason[avId] = reason
def setExceptionInfo(self, exception):
avId = self.air.getAvatarIdFromSender()
self.air.writeServerEvent('client-exception', avId, exception)
def getDisconnectReason(self, avId):
return self.avId2DcReason.get(avId, 0)

0
otp/ai/__init__.py Executable file
View file

568
otp/avatar/Avatar.py Executable file
View file

@ -0,0 +1,568 @@
from direct.actor.Actor import Actor
from direct.directnotify import DirectNotifyGlobal
from direct.distributed import ClockDelta
from direct.interval.IntervalGlobal import *
from direct.showbase.PythonUtil import recordCreationStack
from panda3d.core import *
import random
from otp.ai import MagicWordManager
from otp.ai.MagicWordGlobal import *
from otp.avatar.ShadowCaster import ShadowCaster
from otp.chat import ChatUtil
from otp.otpbase import OTPGlobals
from otp.otpbase import OTPLocalizer
from otp.otpbase import OTPRender
from otp.nametag.Nametag import Nametag
from otp.nametag.NametagGroup import NametagGroup
from otp.nametag.NametagConstants import *
teleportNotify = DirectNotifyGlobal.directNotify.newCategory('Teleport')
teleportNotify.showTime = True
if config.GetBool('want-teleport-debug', 1):
teleportNotify.setDebug(1)
def reconsiderAllUnderstandable():
for av in Avatar.ActiveAvatars:
av.considerUnderstandable()
class Avatar(Actor, ShadowCaster):
notify = directNotify.newCategory('Avatar')
ActiveAvatars = []
def __init__(self, other = None):
Actor.__init__(self, None, None, other, flattenable=0, setFinal=1)
ShadowCaster.__init__(self)
self.__font = OTPGlobals.getInterfaceFont()
self.name = ''
self.soundChatBubble = None
self.avatarType = ''
self.nametagNodePath = None
self.__nameVisible = 1
self.nametag = NametagGroup()
self.nametag.setAvatar(self)
self.nametag.setFont(OTPGlobals.getInterfaceFont())
self.nametag.setSpeechFont(OTPGlobals.getInterfaceFont())
self.nametag2dContents = Nametag.CName | Nametag.CSpeech
self.nametag2dDist = Nametag.CName | Nametag.CSpeech
self.nametag2dNormalContents = Nametag.CName | Nametag.CSpeech
self.nametag3d = self.attachNewNode('nametag3d')
self.nametag3d.setTag('cam', 'nametag')
self.nametag3d.setLightOff()
self.getGeomNode().showThrough(OTPRender.ShadowCameraBitmask)
self.nametag3d.hide(OTPRender.ShadowCameraBitmask)
self.collTube = None
self.scale = 1.0
self.height = 0.0
self.style = None
self.understandable = 1
self.setPlayerType(NametagGroup.CCNormal)
self.ghostMode = 0
self.__chatParagraph = None
self.__chatMessage = None
self.__chatFlags = 0
self.__chatPageNumber = None
self.__chatAddressee = None
self.__chatDialogueList = []
self.__chatSet = 0
self.__chatLocal = 0
self.__currentDialogue = None
self.wantAdminTag = True
def delete(self):
try:
self.Avatar_deleted
except:
self.deleteNametag3d()
Actor.cleanup(self)
self.Avatar_deleted = 1
del self.__font
del self.style
del self.soundChatBubble
self.nametag.destroy()
del self.nametag
self.nametag3d.removeNode()
ShadowCaster.delete(self)
Actor.delete(self)
def isLocal(self):
return 0
def isPet(self):
return False
def isProxy(self):
return False
def setPlayerType(self, playerType):
self.playerType = playerType
if not hasattr(self, 'nametag'):
self.notify.warning('no nametag attributed, but would have been used.')
return
if self.isUnderstandable():
self.nametag.setColorCode(self.playerType)
else:
self.nametag.setColorCode(NametagGroup.CCNonPlayer)
self.setNametagName()
def considerUnderstandable(self):
if self.playerType in (NametagGroup.CCNormal, NametagGroup.CCSpeedChat):
self.setPlayerType(NametagGroup.CCSpeedChat)
if hasattr(base, 'localAvatar') and (self == base.localAvatar):
self.understandable = 1
self.setPlayerType(NametagGroup.CCNormal)
elif self.playerType == NametagGroup.CCSuit:
self.understandable = 1
self.setPlayerType(NametagGroup.CCSuit)
elif self.playerType not in (NametagGroup.CCNormal, NametagGroup.CCSpeedChat):
self.understandable = 1
self.setPlayerType(NametagGroup.CCNonPlayer)
elif base.localAvatar.isTrueFriends(self.doId):
self.understandable = 2
self.setPlayerType(NametagGroup.CCNormal)
elif base.cr.wantSpeedchatPlus():
self.understandable = 1
self.setPlayerType(NametagGroup.CCSpeedChat)
else:
self.understandable = 0
self.setPlayerType(NametagGroup.CCSpeedChat)
if base.cr.wantSpeedchatPlus() and hasattr(self, 'adminAccess') and self.isAdmin() and self != base.localAvatar:
self.understandable = 2
if not hasattr(self, 'nametag'):
self.notify.warning('no nametag attributed, but would have been used')
else:
self.nametag.setColorCode(self.playerType)
def isUnderstandable(self):
return self.understandable
def setDNAString(self, dnaString):
pass
def setDNA(self, dna):
pass
def getAvatarScale(self):
return self.scale
def setAvatarScale(self, scale):
if self.scale != scale:
self.scale = scale
self.getGeomNode().setScale(scale)
self.setHeight(self.height)
def adjustNametag3d(self, parentScale = 1.0):
self.nametag3d.setPos(0, 0, self.height + 0.5)
def getHeight(self):
return self.height
def setHeight(self, height):
self.height = height
self.adjustNametag3d()
if self.collTube:
self.collTube.setPointB(0, 0, height - self.getRadius())
if self.collNodePath:
self.collNodePath.forceRecomputeBounds()
def getRadius(self):
return OTPGlobals.AvatarDefaultRadius
def getName(self):
return self.name
def getType(self):
return self.avatarType
def setWantAdminTag(self, bool):
self.wantAdminTag = bool
def getWantAdminTag(self):
return self.wantAdminTag
def setName(self, name):
if hasattr(self, 'isDisguised') and self.isDisguised:
return
self.name = name
if hasattr(self, 'nametag'):
self.setNametagName()
def setDisplayName(self, str):
if hasattr(self, 'isDisguised'):
if self.isDisguised:
return
self.setNametagName(str)
def setNametagName(self, name=None):
if not name:
name = self.name
self.nametag.setName(name)
if hasattr(self, 'adminAccess') and self.isAdmin() and self.getWantAdminTag():
access = self.getAdminAccess()
if access in OTPLocalizer.AccessToString:
name += '\n\x01shadow\x01%s\x02' % OTPLocalizer.AccessToString[access]
self.nametag.setDisplayName(name)
def getFont(self):
return self.__font
def setFont(self, font):
self.__font = font
self.nametag.setFont(font)
def getStyle(self):
return self.style
def setStyle(self, style):
self.style = style
def getDialogueArray(self):
return None
def playCurrentDialogue(self, dialogue, chatFlags, interrupt = 1):
if interrupt and self.__currentDialogue is not None:
self.__currentDialogue.stop()
self.__currentDialogue = dialogue
if dialogue:
base.playSfx(dialogue, node=self)
elif chatFlags & CFSpeech != 0 and self.nametag.getNumChatPages() > 0:
self.playDialogueForString(self.nametag.getChat())
if self.soundChatBubble != None:
base.playSfx(self.soundChatBubble, node=self)
def playDialogueForString(self, chatString):
searchString = chatString.lower()
if searchString.find(OTPLocalizer.DialogSpecial) >= 0:
type = 'special'
elif searchString.find(OTPLocalizer.DialogExclamation) >= 0:
type = 'exclamation'
elif searchString.find(OTPLocalizer.DialogQuestion) >= 0:
type = 'question'
elif random.randint(0, 1):
type = 'statementA'
else:
type = 'statementB'
stringLength = len(chatString)
if stringLength <= OTPLocalizer.DialogLength1:
length = 1
elif stringLength <= OTPLocalizer.DialogLength2:
length = 2
elif stringLength <= OTPLocalizer.DialogLength3:
length = 3
else:
length = 4
self.playDialogue(type, length)
def playDialogue(self, type, length):
dialogueArray = self.getDialogueArray()
if dialogueArray == None:
return
sfxIndex = None
if type == 'statementA' or type == 'statementB':
if length == 1:
sfxIndex = 0
elif length == 2:
sfxIndex = 1
elif length >= 3:
sfxIndex = 2
elif type == 'question':
sfxIndex = 3
elif type == 'exclamation':
sfxIndex = 4
elif type == 'special':
sfxIndex = 5
else:
notify.error('unrecognized dialogue type: ', type)
if sfxIndex != None and sfxIndex < len(dialogueArray) and dialogueArray[sfxIndex] != None:
base.playSfx(dialogueArray[sfxIndex], node=self)
return
def getDialogueSfx(self, type, length):
retval = None
dialogueArray = self.getDialogueArray()
if dialogueArray == None:
return
sfxIndex = None
if type == 'statementA' or type == 'statementB':
if length == 1:
sfxIndex = 0
elif length == 2:
sfxIndex = 1
elif length >= 3:
sfxIndex = 2
elif type == 'question':
sfxIndex = 3
elif type == 'exclamation':
sfxIndex = 4
elif type == 'special':
sfxIndex = 5
else:
notify.error('unrecognized dialogue type: ', type)
if sfxIndex != None and sfxIndex < len(dialogueArray) and dialogueArray[sfxIndex] != None:
retval = dialogueArray[sfxIndex]
return retval
def setChatAbsolute(self, chatString, chatFlags, dialogue=None, interrupt=1):
self.clearChat()
self.nametag.setChat(chatString, chatFlags)
self.playCurrentDialogue(dialogue, chatFlags, interrupt)
def displayTalk(self, chatString):
if not base.localAvatar.isIgnored(self.doId):
self.clearChat()
if ChatUtil.isThought(chatString):
chatString = ChatUtil.removeThoughtPrefix(chatString)
self.nametag.setChat(chatString, CFThought)
else:
self.nametag.setChat(chatString, CFSpeech | CFTimeout)
def clearChat(self):
self.nametag.clearChat()
def getNameVisible(self):
return self.__nameVisible
def setNameVisible(self, bool):
self.__nameVisible = bool
if bool:
self.showName()
if not bool:
self.hideName()
def hideName(self):
nametag3d = self.nametag.getNametag3d()
nametag3d.setContents(Nametag.CSpeech | Nametag.CThought)
def showName(self):
if self.__nameVisible and (not self.ghostMode):
nametag3d = self.nametag.getNametag3d()
nametag3d.setContents(Nametag.CName | Nametag.CSpeech | Nametag.CThought)
def hideNametag2d(self):
nametag2d = self.nametag.getNametag2d()
self.nametag2dContents = 0
nametag2d.setContents(self.nametag2dContents & self.nametag2dDist)
def showNametag2d(self):
nametag2d = self.nametag.getNametag2d()
self.nametag2dContents = self.nametag2dNormalContents
if self.ghostMode:
self.nametag2dContents = Nametag.CSpeech
nametag2d.setContents(self.nametag2dContents & self.nametag2dDist)
def hideNametag3d(self):
nametag3d = self.nametag.getNametag3d()
nametag3d.setContents(0)
def showNametag3d(self):
nametag3d = self.nametag.getNametag3d()
if self.__nameVisible and (not self.ghostMode):
nametag3d.setContents(Nametag.CName | Nametag.CSpeech | Nametag.CThought)
else:
nametag3d.setContents(0)
def setPickable(self, flag):
self.nametag.setActive(flag)
def clickedNametag(self):
MagicWordManager.lastClickedNametag = self
if self.nametag.hasButton():
self.advancePageNumber()
elif self.nametag.isActive():
messenger.send('clickedNametag', [self])
def setPageChat(self, addressee, paragraph, message, quitButton,
extraChatFlags=None, dialogueList=[], pageButton=True):
self.__chatAddressee = addressee
self.__chatPageNumber = None
self.__chatParagraph = paragraph
self.__chatMessage = message
if extraChatFlags is None:
self.__chatFlags = CFSpeech
else:
self.__chatFlags = CFSpeech | extraChatFlags
self.__chatDialogueList = dialogueList
self.__chatSet = 0
self.__chatLocal = 0
self.__updatePageChat()
if addressee == base.localAvatar.doId:
if pageButton:
self.__chatFlags |= CFPageButton
if quitButton == None:
self.__chatFlags |= CFNoQuitButton
elif quitButton:
self.__chatFlags |= CFQuitButton
self.b_setPageNumber(self.__chatParagraph, 0)
def setLocalPageChat(self, message, quitButton, extraChatFlags=None,
dialogueList=[]):
self.__chatAddressee = base.localAvatar.doId
self.__chatPageNumber = None
self.__chatParagraph = None
self.__chatMessage = message
if extraChatFlags is None:
self.__chatFlags = CFSpeech
else:
self.__chatFlags = CFSpeech | extraChatFlags
self.__chatDialogueList = dialogueList
self.__chatSet = 1
self.__chatLocal = 1
self.__chatFlags |= CFPageButton
if quitButton == None:
self.__chatFlags |= CFNoQuitButton
elif quitButton:
self.__chatFlags |= CFQuitButton
if len(dialogueList) > 0:
dialogue = dialogueList[0]
else:
dialogue = None
self.clearChat()
self.setChatAbsolute(message, self.__chatFlags, dialogue)
self.setPageNumber(None, 0)
def setPageNumber(self, paragraph, pageNumber, timestamp=None):
if timestamp is None:
elapsed = 0.0
else:
elapsed = ClockDelta.globalClockDelta.localElapsedTime(timestamp)
self.__chatPageNumber = [paragraph, pageNumber]
self.__updatePageChat()
if hasattr(self, 'uniqueName'):
if pageNumber >= 0:
messenger.send(self.uniqueName('nextChatPage'), [pageNumber, elapsed])
else:
messenger.send(self.uniqueName('doneChatPage'), [elapsed])
elif pageNumber >= 0:
messenger.send('nextChatPage', [pageNumber, elapsed])
else:
messenger.send('doneChatPage', [elapsed])
def advancePageNumber(self):
if (self.__chatAddressee == base.localAvatar.doId) and (
self.__chatPageNumber is not None) and (
self.__chatPageNumber[0] == self.__chatParagraph):
pageNumber = self.__chatPageNumber[1]
if pageNumber >= 0:
pageNumber += 1
if pageNumber >= self.nametag.getNumChatPages():
pageNumber = -1
if self.__chatLocal:
self.setPageNumber(self.__chatParagraph, pageNumber)
else:
self.b_setPageNumber(self.__chatParagraph, pageNumber)
def __updatePageChat(self):
if (self.__chatPageNumber is not None) and (
self.__chatPageNumber[0] == self.__chatParagraph):
pageNumber = self.__chatPageNumber[1]
if pageNumber >= 0:
if not self.__chatSet:
if len(self.__chatDialogueList) > 0:
dialogue = self.__chatDialogueList[0]
else:
dialogue = None
self.setChatAbsolute(self.__chatMessage, self.__chatFlags, dialogue)
self.__chatSet = 1
if pageNumber < self.nametag.getNumChatPages():
self.nametag.setPageNumber(pageNumber)
if pageNumber > 0:
if len(self.__chatDialogueList) > pageNumber:
dialogue = self.__chatDialogueList[pageNumber]
else:
dialogue = None
self.playCurrentDialogue(dialogue, self.__chatFlags)
else:
self.clearChat()
else:
self.clearChat()
def getAirborneHeight(self):
height = self.getPos(self.shadowPlacer.shadowNodePath)
return height.getZ() + 0.025
def initializeNametag3d(self):
self.deleteNametag3d()
nametagNode = self.nametag.getNametag3d()
self.nametagNodePath = self.nametag3d.attachNewNode(nametagNode)
iconNodePath = self.nametag.getNameIcon()
for cJoint in self.getNametagJoints():
cJoint.clearNetTransforms()
cJoint.addNetTransform(nametagNode)
def deleteNametag3d(self):
if self.nametagNodePath:
self.nametagNodePath.removeNode()
self.nametagNodePath = None
def initializeBodyCollisions(self, collIdStr):
self.collTube = CollisionTube(0, 0, 0.5, 0, 0, self.height - self.getRadius(), self.getRadius())
self.collNode = CollisionNode(collIdStr)
self.collNode.addSolid(self.collTube)
self.collNodePath = self.attachNewNode(self.collNode)
if self.ghostMode:
self.collNode.setCollideMask(OTPGlobals.GhostBitmask)
else:
self.collNode.setCollideMask(OTPGlobals.WallBitmask)
def stashBodyCollisions(self):
if hasattr(self, 'collNodePath'):
self.collNodePath.stash()
def unstashBodyCollisions(self):
if hasattr(self, 'collNodePath'):
self.collNodePath.unstash()
def disableBodyCollisions(self):
if hasattr(self, 'collNodePath'):
self.collNodePath.removeNode()
del self.collNodePath
self.collTube = None
return
def addActive(self):
if base.wantNametags:
try:
Avatar.ActiveAvatars.remove(self)
except ValueError:
pass
Avatar.ActiveAvatars.append(self)
self.nametag.manage(base.marginManager)
self.accept(self.nametag.getUniqueId(), self.clickedNametag)
def removeActive(self):
if base.wantNametags:
try:
Avatar.ActiveAvatars.remove(self)
except ValueError:
pass
self.nametag.unmanage(base.marginManager)
self.ignore(self.nametag.getUniqueId())
def loop(self, animName, restart = 1, partName = None, fromFrame = None, toFrame = None):
return Actor.loop(self, animName, restart, partName, fromFrame, toFrame)
def createTalkSequence(self, speech, waitTime, name='talkSequence'):
sequence = Sequence(name=name)
for text in speech:
sequence.append(Func(self.setChatAbsolute, text, CFSpeech))
sequence.append(Wait(len(text.split(' '))))
sequence.append(Func(self.clearChat))
sequence.append(Wait(waitTime))
return sequence
@magicWord(category=CATEGORY_COMMUNITY_MANAGER)
def target():
"""
Returns the current Spellbook target.
"""
return 'Your current target is: %s [avId: %s, access: %s]' % (spellbook.getTarget().getName(), spellbook.getTarget().doId, spellbook.getTarget().getAdminAccess())

51
otp/avatar/AvatarDetail.py Executable file
View file

@ -0,0 +1,51 @@
from direct.directnotify.DirectNotifyGlobal import directNotify
from otp.avatar import Avatar
class AvatarDetail:
notify = directNotify.newCategory('AvatarDetail')
def __init__(self, doId, callWhenDone):
self.id = doId
self.callWhenDone = callWhenDone
self.enterQuery()
def isReady(self):
return true
def getId(self):
return self.id
def enterQuery(self):
self.avatar = base.cr.doId2do.get(self.id)
if self.avatar != None and not self.avatar.ghostMode:
self.createdAvatar = 0
dclass = self.getDClass()
self.__handleResponse(True, self.avatar, dclass)
else:
self.avatar = self.createHolder()
self.createdAvatar = 1
self.avatar.doId = self.id
dclass = self.getDClass()
base.cr.getAvatarDetails(self.avatar, self.__handleResponse, dclass)
return
def exitQuery(self):
return true
def createHolder(self):
pass
def getDClass(self):
pass
def __handleResponse(self, gotData, avatar, dclass):
if avatar != self.avatar:
self.notify.warning('Ignoring unexpected request for avatar %s' % avatar.doId)
return
if gotData:
self.callWhenDone(self.avatar)
del self.callWhenDone
else:
self.callWhenDone(None)
del self.callWhenDone
return

75
otp/avatar/AvatarPanel.py Executable file
View file

@ -0,0 +1,75 @@
from panda3d.core import *
from direct.gui.DirectGui import *
from direct.showbase import DirectObject
import Avatar
from direct.distributed import DistributedObject
class AvatarPanel(DirectObject.DirectObject):
currentAvatarPanel = None
def __init__(self, avatar, FriendsListPanel = None):
if AvatarPanel.currentAvatarPanel:
AvatarPanel.currentAvatarPanel.cleanup()
AvatarPanel.currentAvatarPanel = self
self.friendsListShown = False
self.FriendsListPanel = FriendsListPanel
if FriendsListPanel:
self.friendsListShown = FriendsListPanel.isFriendsListShown()
FriendsListPanel.hideFriendsList()
if avatar:
self.avatar = avatar
self.avName = avatar.getName()
else:
self.avatar = None
self.avName = 'Avatar'
if hasattr(avatar, 'uniqueName'):
self.avId = avatar.doId
self.avDisableName = avatar.uniqueName('disable')
self.avGenerateName = avatar.uniqueName('generate')
self.avHpChangeName = avatar.uniqueName('hpChange')
if self.avId in base.cr.doId2do:
self.avatar = base.cr.doId2do[self.avId]
else:
self.avDisableName = None
self.avGenerateName = None
self.avHpChangeName = None
self.avId = None
if self.avDisableName:
self.accept(self.avDisableName, self.__handleDisableAvatar)
return
def cleanup(self):
if AvatarPanel.currentAvatarPanel != self:
return
if self.avDisableName:
self.ignore(self.avDisableName)
if self.avGenerateName:
self.ignore(self.avGenerateName)
if self.avHpChangeName:
self.ignore(self.avHpChangeName)
AvatarPanel.currentAvatarPanel = None
return
def __handleClose(self):
self.cleanup()
AvatarPanel.currentAvatarPanel = None
if self.friendsListShown:
self.FriendsListPanel.showFriendsList()
return
def __handleDisableAvatar(self):
if AvatarPanel.currentAvatarPanel:
AvatarPanel.currentAvatarPanel.handleDisableAvatar()
else:
self.handleDisableAvatar()
def handleDisableAvatar(self):
self.cleanup()
AvatarPanel.currentAvatarPanel = None
return
def isHidden(self):
return 1
def getType(self):
return None

346
otp/avatar/DistributedAvatar.py Executable file
View file

@ -0,0 +1,346 @@
from direct.actor.DistributedActor import DistributedActor
from direct.distributed import DistributedNode
from direct.interval.IntervalGlobal import *
from direct.showbase import PythonUtil
from direct.task import Task
from panda3d.core import *
from Avatar import Avatar
from otp.ai.MagicWordGlobal import *
from otp.otpbase import OTPGlobals
from toontown.battle.BattleProps import globalPropPool
from otp.nametag.Nametag import Nametag
class DistributedAvatar(DistributedActor, Avatar):
HpTextGenerator = TextNode('HpTextGenerator')
HpTextEnabled = 1
ManagesNametagAmbientLightChanged = True
def __init__(self, cr):
try:
self.DistributedAvatar_initialized
return
except:
self.DistributedAvatar_initialized = 1
Avatar.__init__(self)
DistributedActor.__init__(self, cr)
self.hpText = None
self.hp = None
self.maxHp = None
return
def disable(self):
try:
del self.DistributedAvatar_announced
except:
return
self.reparentTo(hidden)
self.removeActive()
self.disableBodyCollisions()
self.hideHpText()
self.hp = None
self.ignore('nameTagShowAvId')
self.ignore('nameTagShowName')
DistributedActor.disable(self)
return
def delete(self):
try:
self.DistributedAvatar_deleted
except:
self.DistributedAvatar_deleted = 1
Avatar.delete(self)
DistributedActor.delete(self)
def generate(self):
DistributedActor.generate(self)
if not self.isLocal():
self.addActive()
self.considerUnderstandable()
self.setParent(OTPGlobals.SPHidden)
self.setTag('avatarDoId', str(self.doId))
self.accept('nameTagShowAvId', self.__nameTagShowAvId)
self.accept('nameTagShowName', self.__nameTagShowName)
def announceGenerate(self):
try:
self.DistributedAvatar_announced
return
except:
self.DistributedAvatar_announced = 1
if not self.isLocal():
self.initializeBodyCollisions('distAvatarCollNode-' + str(self.doId))
DistributedActor.announceGenerate(self)
def __setTags(self, extra = None):
if hasattr(base, 'idTags'):
if base.idTags:
self.__nameTagShowAvId()
else:
self.__nameTagShowName()
def do_setParent(self, parentToken):
if not self.isDisabled():
if parentToken == OTPGlobals.SPHidden:
self.nametag2dDist &= ~Nametag.CName
else:
self.nametag2dDist |= Nametag.CName
self.nametag.getNametag2d().setContents(self.nametag2dContents & self.nametag2dDist)
DistributedActor.do_setParent(self, parentToken)
self.__setTags()
def toonUp(self, hpGained):
if self.hp == None or hpGained < 0:
return
oldHp = self.hp
if self.hp + hpGained <= 0:
self.hp += hpGained
else:
self.hp = min(max(self.hp, 0) + hpGained, self.maxHp)
hpGained = self.hp - max(oldHp, 0)
if hpGained > 0:
self.showHpText(hpGained)
self.hpChange(quietly=0)
return
def takeDamage(self, hpLost, bonus = 0):
if self.hp == None or hpLost < 0:
return
oldHp = self.hp
self.hp = max(self.hp - hpLost, 0)
hpLost = oldHp - self.hp
if hpLost > 0:
self.showHpText(-hpLost, bonus)
self.hpChange(quietly=0)
if self.hp <= 0 and oldHp > 0:
self.died()
return
def setHp(self, hitPoints):
justRanOutOfHp = (hitPoints is not None and self.hp is not None and self.hp - hitPoints > 0) and (hitPoints <= 0)
self.hp = hitPoints
self.hpChange(quietly=1)
if justRanOutOfHp:
self.died()
return
def hpChange(self, quietly = 0):
if (not hasattr(self, 'doId')) or self.hp == None:
return
if self.maxHp != None:
messenger.send(self.uniqueName('hpChange'), [self.hp, self.maxHp, quietly])
if self.hp > 0:
messenger.send(self.uniqueName('positiveHP'))
def died(self):
pass
def getHp(self):
return self.hp
def setMaxHp(self, hitPoints):
self.maxHp = hitPoints
self.hpChange()
def getMaxHp(self):
return self.maxHp
def getName(self):
return Avatar.getName(self)
def setName(self, name):
try:
self.node().setName('%s-%d' % (name, self.doId))
self.gotName = 1
except:
pass
return Avatar.setName(self, name)
def showHpText(self, number, bonus = 0, scale = 1):
if self.HpTextEnabled and not self.ghostMode:
if number != 0:
if self.hpText:
self.hideHpText()
self.HpTextGenerator.setFont(OTPGlobals.getSignFont())
if number < 0:
self.HpTextGenerator.setText(str(number))
else:
self.HpTextGenerator.setText('+' + str(number))
self.HpTextGenerator.clearShadow()
self.HpTextGenerator.setAlign(TextNode.ACenter)
if bonus == 1:
r = 1.0
g = 1.0
b = 0
a = 1
elif bonus == 2:
r = 1.0
g = 0.5
b = 0
a = 1
elif number < 0:
r = 0.9
g = 0
b = 0
a = 1
else:
r = 0
g = 0.9
b = 0
a = 1
self.HpTextGenerator.setTextColor(r, g, b, a)
self.hpTextNode = self.HpTextGenerator.generate()
self.hpText = self.attachNewNode(self.hpTextNode)
self.hpText.setScale(scale)
self.hpText.setBillboardPointEye()
self.hpText.setBin('fixed', 100)
self.hpText.setPos(0, 0, self.height / 2)
seq = Sequence(self.hpText.posInterval(1.0, Point3(0, 0, self.height + 1.5), blendType='easeOut'), Wait(0.85), self.hpText.colorInterval(0.1, Vec4(r, g, b, 0)), Func(self.hideHpText))
seq.start()
def showHpString(self, text, duration = 0.85, scale = 0.7):
if self.HpTextEnabled and not self.ghostMode:
if text != '':
if self.hpText:
self.hideHpText()
self.HpTextGenerator.setFont(OTPGlobals.getSignFont())
self.HpTextGenerator.setText(text)
self.HpTextGenerator.clearShadow()
self.HpTextGenerator.setAlign(TextNode.ACenter)
r = a = 1.0
g = b = 0.0
self.HpTextGenerator.setTextColor(r, g, b, a)
self.hpTextNode = self.HpTextGenerator.generate()
self.hpText = self.attachNewNode(self.hpTextNode)
self.hpText.setScale(scale)
self.hpText.setBillboardAxis()
self.hpText.setPos(0, 0, self.height / 2)
seq = Sequence(self.hpText.posInterval(1.0, Point3(0, 0, self.height + 1.5), blendType='easeOut'), Wait(duration), self.hpText.colorInterval(0.1, Vec4(r, g, b, 0)), Func(self.hideHpText))
seq.start()
def hideHpText(self):
if self.hpText:
taskMgr.remove(self.uniqueName('hpText'))
self.hpText.removeNode()
self.hpText = None
return
def getStareAtNodeAndOffset(self):
return (self, Point3(0, 0, self.height))
def getAvIdName(self):
return '%s\n%s' % (self.getName(), self.doId)
def __nameTagShowAvId(self, extra = None):
self.setDisplayName(self.getAvIdName())
def __nameTagShowName(self, extra = None):
self.setDisplayName(self.getName())
def askAvOnShard(self, avId):
if base.cr.doId2do.get(avId):
messenger.send('AvOnShard%s' % avId, [True])
else:
self.sendUpdate('checkAvOnShard', [avId])
def confirmAvOnShard(self, avId, onShard = True):
messenger.send('AvOnShard%s' % avId, [onShard])
def getDialogueArray(self):
return None
@magicWord(category=CATEGORY_COMMUNITY_MANAGER)
def warp():
"""
warp the target to the invoker's current position, and rotation.
"""
invoker = spellbook.getInvoker()
target = spellbook.getTarget()
if invoker.doId == target.doId:
return "You can't warp yourself!"
target.setPosHpr(invoker.getPos(), invoker.getHpr())
@magicWord(category=CATEGORY_COMMUNITY_MANAGER, types=[str])
def loop(anim):
"""
animate the target using animation [anim] on the entire actor.
"""
target = spellbook.getTarget()
target.loop(anim)
@magicWord(category=CATEGORY_COMMUNITY_MANAGER, types=[str, int, str])
def pose(anim, frame, part=None):
"""
freeze the target on frame [frame] of animation [anim] on the entire actor,
or optional [part] of the actor.
"""
target = spellbook.getTarget()
target.pose(anim, frame, partName=part)
@magicWord(category=CATEGORY_COMMUNITY_MANAGER, types=[str, int, int, str])
def pingpong(anim, start=None, end=None, part=None):
"""
animate the target by bouncing back and forth between the start and end, or
the optional frames <start>, and [end] of animation [anim] on the entire
actor, or optional <part> of the actor.
"""
target = spellbook.getTarget()
target.pingpong(anim, partName=part, fromFrame=start, toFrame=end)
@magicWord(category=CATEGORY_COMMUNITY_MANAGER, types=[str])
def rightHand(prop=None):
"""
parents the optional <prop> to the target's right hand node.
"""
target = spellbook.getTarget()
rightHand = target.find('**/rightHand')
if prop is None:
for child in rightHand.getChildren():
child.removeNode()
else:
for child in rightHand.getChildren():
child.removeNode()
requestedProp = globalPropPool.getProp(prop)
requestedProp.reparentTo(rightHand)
@magicWord(category=CATEGORY_COMMUNITY_MANAGER, types=[str])
def leftHand(prop=None):
"""
parents the optional <prop> to the target's left hand node.
"""
target = spellbook.getTarget()
leftHand = target.find('**/leftHand')
if prop is None:
for child in leftHand.getChildren():
child.removeNode()
else:
for child in leftHand.getChildren():
child.removeNode()
requestedProp = globalPropPool.getProp(prop)
requestedProp.reparentTo(leftHand)
@magicWord(category=CATEGORY_PROGRAMMER, types=[])
def getPos():
"""
Return your target's position.
"""
return spellbook.getTarget().getPos()
@magicWord(category=CATEGORY_PROGRAMMER, types=[int])
def setFov(fov=OTPGlobals.DefaultCameraFov):
"""
Set your field of view in-game.
"""
if fov == 0:
return 'Cannot set FOV to 0!'
base.camLens.setMinFov(fov/(4./3.))
if fov == OTPGlobals.DefaultCameraFov:
return 'Set FOV to the default.'
else:
return 'Set FOV to %s.' % fov

View file

@ -0,0 +1,68 @@
from otp.ai.AIBaseGlobal import *
from otp.otpbase import OTPGlobals
from direct.distributed import DistributedNodeAI
class DistributedAvatarAI(DistributedNodeAI.DistributedNodeAI):
def __init__(self, air):
DistributedNodeAI.DistributedNodeAI.__init__(self, air)
self.hp = 0
self.maxHp = 0
def b_setName(self, name):
self.setName(name)
self.d_setName(name)
def d_setName(self, name):
self.sendUpdate('setName', [name])
def setName(self, name):
self.name = name
def getName(self):
return self.name
def b_setMaxHp(self, maxHp):
self.d_setMaxHp(maxHp)
self.setMaxHp(maxHp)
def d_setMaxHp(self, maxHp):
self.sendUpdate('setMaxHp', [maxHp])
def setMaxHp(self, maxHp):
self.maxHp = maxHp
def getMaxHp(self):
return self.maxHp
def b_setHp(self, hp):
self.d_setHp(hp)
self.setHp(hp)
def d_setHp(self, hp):
self.sendUpdate('setHp', [hp])
def setHp(self, hp):
self.hp = hp
def getHp(self):
return self.hp
def toonUp(self, num):
if self.hp >= self.maxHp:
return
self.hp = min(self.hp + num, self.maxHp)
self.b_setHp(self.hp)
def getRadius(self):
return OTPGlobals.AvatarDefaultRadius
def checkAvOnShard(self, avId):
self.sendUpdateToAvatarId(self.air.getAvatarIdFromSender(), 'confirmAvOnShard', [avId, avId in self.air.doId2do])
def setParentStr(self, parentToken):
if parentToken:
senderId = self.air.getAvatarIdFromSender()
self.air.writeServerEvent('Admin chat warning', senderId, 'using setParentStr to send "%s"' % parentToken)
self.notify.warning('Admin chat warning: %s using setParentStr to send "%s"' % (senderId, parentToken))
DistributedNodeAI.DistributedNodeAI.setParentStr(self, parentToken)

View file

@ -0,0 +1,17 @@
from direct.directnotify import DirectNotifyGlobal
from direct.distributed.DistributedObjectUD import DistributedObjectUD
class DistributedAvatarUD(DistributedObjectUD):
notify = DirectNotifyGlobal.directNotify.newCategory("DistributedAvatarUD")
def setName(self, todo0):
pass
def friendsNotify(self, todo0, todo1):
pass
def checkAvOnShard(self, todo0):
pass
def confirmAvOnShard(self, todo0, todo1):
pass

357
otp/avatar/DistributedPlayer.py Executable file
View file

@ -0,0 +1,357 @@
from direct.showbase import PythonUtil
from direct.task import Task
from panda3d.core import *
import string
import time
from otp.ai.MagicWordGlobal import *
from otp.avatar import Avatar, PlayerBase, DistributedAvatar
from otp.avatar.Avatar import teleportNotify
from otp.chat import ChatGarbler, TalkAssistant
from otp.distributed.TelemetryLimited import TelemetryLimited
from otp.otpbase import OTPGlobals, OTPLocalizer
from otp.speedchat import SCDecoders
from otp.nametag.NametagConstants import *
from otp.margins.WhisperPopup import WhisperPopup
class DistributedPlayer(DistributedAvatar.DistributedAvatar, PlayerBase.PlayerBase, TelemetryLimited):
chatGarbler = ChatGarbler.ChatGarbler({'default': OTPLocalizer.ChatGarblerDefault})
def __init__(self, cr):
try:
self.DistributedPlayer_initialized
except:
self.DistributedPlayer_initialized = 1
DistributedAvatar.DistributedAvatar.__init__(self, cr)
TelemetryLimited.__init__(self)
self.__teleportAvailable = 0
self.inventory = None
self.experience = None
self.friendsList = []
self._districtWeAreGeneratedOn = None
self.DISLid = 0
self.adminAccess = 0
self.autoRun = 0
self.lastTeleportQuery = time.time()
@staticmethod
def GetPlayerGenerateEvent():
return 'DistributedPlayerGenerateEvent'
@staticmethod
def GetPlayerNetworkDeleteEvent():
return 'DistributedPlayerNetworkDeleteEvent'
@staticmethod
def GetPlayerDeleteEvent():
return 'DistributedPlayerDeleteEvent'
def networkDelete(self):
DistributedAvatar.DistributedAvatar.networkDelete(self)
messenger.send(self.GetPlayerNetworkDeleteEvent(), [self])
def disable(self):
DistributedAvatar.DistributedAvatar.disable(self)
messenger.send(self.GetPlayerDeleteEvent(), [self])
def delete(self):
try:
self.DistributedPlayer_deleted
except:
self.DistributedPlayer_deleted = 1
del self.experience
if self.inventory:
self.inventory.unload()
del self.inventory
DistributedAvatar.DistributedAvatar.delete(self)
def generate(self):
DistributedAvatar.DistributedAvatar.generate(self)
def announceGenerate(self):
DistributedAvatar.DistributedAvatar.announceGenerate(self)
messenger.send(self.GetPlayerGenerateEvent(), [self])
def setLocation(self, parentId, zoneId):
DistributedAvatar.DistributedAvatar.setLocation(self, parentId, zoneId)
if not (parentId in (0, None) and zoneId in (0, None)):
if not self.cr._isValidPlayerLocation(parentId, zoneId):
self.cr.disableDoId(self.doId)
self.cr.deleteObject(self.doId)
return None
def isGeneratedOnDistrict(self, districtId = None):
return True # fix for the task button
if districtId is None:
return self._districtWeAreGeneratedOn is not None
else:
return self._districtWeAreGeneratedOn == districtId
return
def getArrivedOnDistrictEvent(self, districtId = None):
if districtId is None:
return 'arrivedOnDistrict'
else:
return 'arrivedOnDistrict-%s' % districtId
return
def arrivedOnDistrict(self, districtId):
curFrameTime = globalClock.getFrameTime()
if hasattr(self, 'frameTimeWeArrivedOnDistrict') and curFrameTime == self.frameTimeWeArrivedOnDistrict:
if districtId == 0 and self._districtWeAreGeneratedOn:
self.notify.warning('ignoring arrivedOnDistrict 0, since arrivedOnDistrict %d occured on the same frame' % self._districtWeAreGeneratedOn)
return
self._districtWeAreGeneratedOn = districtId
self.frameTimeWeArrivedOnDistrict = globalClock.getFrameTime()
messenger.send(self.getArrivedOnDistrictEvent(districtId))
messenger.send(self.getArrivedOnDistrictEvent())
def setLeftDistrict(self):
self._districtWeAreGeneratedOn = None
return
def hasParentingRules(self):
if self is localAvatar:
return True
def setSystemMessage(self, aboutId, chatString, whisperType = WhisperPopup.WTSystem):
self.displayWhisper(aboutId, chatString, whisperType)
def displayWhisper(self, fromId, chatString, whisperType):
print 'Whisper type %s from %s: %s' % (whisperType, fromId, chatString)
def whisperSCTo(self, msgIndex, sendToId):
messenger.send('wakeup')
base.cr.ttsFriendsManager.d_whisperSCTo(sendToId, msgIndex)
def setWhisperSCFrom(self, fromId, msgIndex):
handle = base.cr.identifyAvatar(fromId)
if handle == None or base.localAvatar.isIgnored(fromId):
return
chatString = SCDecoders.decodeSCStaticTextMsg(msgIndex)
if chatString:
self.displayWhisper(fromId, chatString, WhisperPopup.WTNormal)
return
def whisperSCCustomTo(self, msgIndex, sendToId):
messenger.send('wakeup')
base.cr.ttsFriendsManager.d_whisperSCCustomTo(sendToId, msgIndex)
def _isValidWhisperSource(self, source):
return True
def setWhisperSCCustomFrom(self, fromId, msgIndex):
handle = base.cr.identifyAvatar(fromId)
if handle == None:
return
if not self._isValidWhisperSource(handle):
self.notify.warning('displayWhisper from non-toon %s' % fromId)
return
if base.localAvatar.isIgnored(fromId):
return
chatString = SCDecoders.decodeSCCustomMsg(msgIndex)
if chatString:
self.displayWhisper(fromId, chatString, WhisperPopup.WTNormal)
def whisperSCEmoteTo(self, emoteId, sendToId):
messenger.send('wakeup')
base.cr.ttsFriendsManager.d_whisperSCEmoteTo(sendToId, emoteId)
def setWhisperSCEmoteFrom(self, fromId, emoteId):
handle = base.cr.identifyAvatar(fromId)
if handle == None or base.localAvatar.isIgnored(fromId):
return
chatString = SCDecoders.decodeSCEmoteWhisperMsg(emoteId, handle.getName())
if chatString:
self.displayWhisper(fromId, chatString, WhisperPopup.WTEmote)
return
def setChatAbsolute(self, chatString, chatFlags, dialogue = None, interrupt = 1, quiet = 0):
DistributedAvatar.DistributedAvatar.setChatAbsolute(self, chatString, chatFlags, dialogue, interrupt)
if not quiet:
pass
def setTalk(self, chat):
if not base.cr.chatAgent.verifyMessage(chat):
return
if base.localAvatar.isIgnored(self.doId):
return
if not self.understandable:
chat = self.chatGarbler.garble(self, len(chat.split(' ')))
elif base.whiteList and self.understandable < 2:
chat = base.whiteList.processThroughAll(chat, self, self.chatGarbler)
self.displayTalk(chat)
def setTalkWhisper(self, avId, chat):
if not base.cr.chatAgent.verifyMessage(chat):
return
if base.localAvatar.isIgnored(avId):
return
if not self.understandable:
chat = self.chatGarbler.garble(self, len(chat.split(' ')))
elif base.whiteList and self.understandable < 2:
chat = base.whiteList.processThroughAll(chat, self.chatGarbler)
self.displayTalkWhisper(avId, chat)
def displayTalk(self, chat):
print 'Talk: %s' % chat
def displayTalkWhisper(self, avId, chat):
print 'TalkWhisper from %s: %s' % (avId, chat)
def b_setSC(self, msgIndex):
self.setSC(msgIndex)
self.d_setSC(msgIndex)
def d_setSC(self, msgIndex):
messenger.send('wakeup')
self.sendUpdate('setSC', [msgIndex])
def setSC(self, msgIndex):
if base.localAvatar.isIgnored(self.doId):
return
chatString = SCDecoders.decodeSCStaticTextMsg(msgIndex)
if chatString:
self.setChatAbsolute(chatString, CFSpeech | CFQuicktalker | CFTimeout, quiet=1)
def b_setSCCustom(self, msgIndex):
self.setSCCustom(msgIndex)
self.d_setSCCustom(msgIndex)
def d_setSCCustom(self, msgIndex):
messenger.send('wakeup')
self.sendUpdate('setSCCustom', [msgIndex])
def setSCCustom(self, msgIndex):
if base.localAvatar.isIgnored(self.doId):
return
chatString = SCDecoders.decodeSCCustomMsg(msgIndex)
if chatString:
self.setChatAbsolute(chatString, CFSpeech | CFQuicktalker | CFTimeout)
def b_setSCEmote(self, emoteId):
self.b_setEmoteState(emoteId, animMultiplier=self.animMultiplier)
def d_friendsNotify(self, avId, status):
self.sendUpdate('friendsNotify', [avId, status])
def friendsNotify(self, avId, status):
avatar = base.cr.identifyFriend(avId)
if avatar != None:
if status == 1:
self.setSystemMessage(avId, OTPLocalizer.WhisperNoLongerFriend % avatar.getName())
elif status == 2:
self.setSystemMessage(avId, OTPLocalizer.WhisperNowSpecialFriend % avatar.getName())
return
def d_teleportQuery(self, requesterId, sendToId = None):
lastQuery = self.lastTeleportQuery
currentQuery = time.time()
if currentQuery - lastQuery < 0.1: # Oh boy! We found a skid!
self.cr.stopReaderPollTask()
self.cr.lostConnection()
self.lastTeleportQuery = time.time()
base.cr.ttsFriendsManager.d_teleportQuery(sendToId)
def teleportQuery(self, requesterId):
avatar = base.cr.identifyFriend(requesterId)
if avatar is None:
self.d_teleportResponse(self.doId, 0, 0, 0, 0, sendToId=requesterId)
elif base.localAvatar.isIgnored(requesterId):
self.d_teleportResponse(self.doId, 2, 0, 0, 0, sendToId=requesterId)
elif hasattr(base, 'distributedParty') and ((base.distributedParty.partyInfo.isPrivate and requesterId not in base.distributedParty.inviteeIds) or base.distributedParty.isPartyEnding):
self.d_teleportResponse(self.doId, 0, 0, 0, 0, sendToId=requesterId)
elif self.__teleportAvailable and not self.ghostMode:
self.setSystemMessage(requesterId, OTPLocalizer.WhisperComingToVisit % avatar.getName())
messenger.send('teleportQuery', [avatar, self])
else:
self.setSystemMessage(requesterId, OTPLocalizer.WhisperFailedVisit % avatar.getName())
self.d_teleportResponse(self.doId, 0, 0, 0, 0, sendToId=requesterId)
def d_teleportResponse(self, avId, available, shardId, hoodId, zoneId, sendToId):
teleportNotify.debug('sending teleportResponse%s' % ((avId, available,
shardId, hoodId, zoneId, sendToId),)
)
base.cr.ttsFriendsManager.d_teleportResponse(sendToId, available,
shardId, hoodId, zoneId
)
def teleportResponse(self, avId, available, shardId, hoodId, zoneId):
teleportNotify.debug('received teleportResponse%s' % ((avId, available,
shardId, hoodId, zoneId),)
)
messenger.send('teleportResponse', [avId, available, shardId, hoodId, zoneId])
def d_teleportGiveup(self, requesterId, sendToId):
teleportNotify.debug('sending teleportGiveup(%s) to %s' % (requesterId, sendToId))
base.cr.ttsFriendsManager.d_teleportGiveup(sendToId)
def teleportGiveup(self, requesterId):
teleportNotify.debug('received teleportGiveup(%s)' % (requesterId,))
avatar = base.cr.identifyAvatar(requesterId)
if not self._isValidWhisperSource(avatar):
self.notify.warning('teleportGiveup from non-toon %s' % requesterId)
return
if avatar is not None:
self.setSystemMessage(requesterId,
OTPLocalizer.WhisperGiveupVisit % avatar.getName()
)
def b_teleportGreeting(self, avId):
if hasattr(self, 'ghostMode') and self.ghostMode:
return
self.d_teleportGreeting(avId)
self.teleportGreeting(avId)
def d_teleportGreeting(self, avId):
self.sendUpdate('teleportGreeting', [avId])
def teleportGreeting(self, avId):
avatar = base.cr.getDo(avId)
if isinstance(avatar, Avatar.Avatar):
self.setChatAbsolute(OTPLocalizer.TeleportGreeting % avatar.getName(), CFSpeech | CFTimeout)
elif avatar is not None:
self.notify.warning('got teleportGreeting from %s referencing non-toon %s' % (self.doId, avId))
return
def setTeleportAvailable(self, available):
self.__teleportAvailable = available
def getTeleportAvailable(self):
return self.__teleportAvailable
def getFriendsList(self):
return self.friendsList
def setFriendsList(self, friendsList):
self.friendsList = friendsList
messenger.send('friendsListChanged')
Avatar.reconsiderAllUnderstandable()
def setDISLid(self, id):
self.DISLid = id
def setAdminAccess(self, access):
self.adminAccess = access
self.considerUnderstandable()
def getAdminAccess(self):
return self.adminAccess
def isAdmin(self):
return self.adminAccess >= MINIMUM_MAGICWORD_ACCESS
def setAutoRun(self, value):
self.autoRun = value
def getAutoRun(self):
return self.autoRun

251
otp/avatar/DistributedPlayerAI.py Executable file
View file

@ -0,0 +1,251 @@
from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.MsgTypes import CLIENTAGENT_EJECT
from otp.ai.AIBaseGlobal import *
from otp.ai.MagicWordGlobal import *
from otp.avatar import DistributedAvatarAI
from otp.avatar import PlayerBase
from otp.distributed import OtpDoGlobals
from otp.otpbase import OTPLocalizer
class DistributedPlayerAI(DistributedAvatarAI.DistributedAvatarAI, PlayerBase.PlayerBase):
def __init__(self, air):
DistributedAvatarAI.DistributedAvatarAI.__init__(self, air)
self.friendsList = []
self.DISLid = 0
self.adminAccess = 0
def announceGenerate(self):
DistributedAvatarAI.DistributedAvatarAI.announceGenerate(self)
self._doPlayerEnter()
def _announceArrival(self):
self.sendUpdate('arrivedOnDistrict', [self.air.districtId])
def _announceExit(self):
self.sendUpdate('arrivedOnDistrict', [0])
def _sendExitServerEvent(self):
self.air.writeServerEvent('avatarExit', self.doId, '')
def delete(self):
self._doPlayerExit()
DistributedAvatarAI.DistributedAvatarAI.delete(self)
def isPlayerControlled(self):
return True
def setLocation(self, parentId, zoneId):
DistributedAvatarAI.DistributedAvatarAI.setLocation(self, parentId, zoneId)
if self.isPlayerControlled():
if not self.air._isValidPlayerLocation(parentId, zoneId):
self.notify.info('booting player %s for doing setLocation to (%s, %s)' % (self.doId, parentId, zoneId))
self.air.writeServerEvent('suspicious', self.doId, 'invalid setLocation: (%s, %s)' % (parentId, zoneId))
self.requestDelete()
def _doPlayerEnter(self):
self.incrementPopulation()
self._announceArrival()
def _doPlayerExit(self):
self._announceExit()
self.decrementPopulation()
def incrementPopulation(self):
self.air.incrementPopulation()
def decrementPopulation(self):
simbase.air.decrementPopulation()
def d_setMaxHp(self, maxHp):
DistributedAvatarAI.DistributedAvatarAI.d_setMaxHp(self, maxHp)
self.air.writeServerEvent('setMaxHp', self.doId, '%s' % maxHp)
def d_setSystemMessage(self, aboutId, chatString):
self.sendUpdate('setSystemMessage', [aboutId, chatString])
def d_friendsNotify(self, avId, status):
self.sendUpdate('friendsNotify', [avId, status])
def friendsNotify(self, avId, status):
pass
def setDISLid(self, id):
self.DISLid = id
def getDISLid(self):
return self.DISLid
def d_setFriendsList(self, friendsList):
self.sendUpdate('setFriendsList', [friendsList])
def setFriendsList(self, friendsList):
self.friendsList = friendsList
self.notify.debug('setting friends list to %s' % self.friendsList)
def getFriendsList(self):
return self.friendsList
def setAdminAccess(self, access):
self.adminAccess = access
def d_setAdminAccess(self, access):
self.sendUpdate('setAdminAccess', [access])
def b_setAdminAccess(self, access):
self.setAdminAccess(access)
self.d_setAdminAccess(access)
def getAdminAccess(self):
return self.adminAccess
def isAdmin(self):
return self.adminAccess >= MINIMUM_MAGICWORD_ACCESS
def extendFriendsList(self, friendId):
if friendId in self.friendsList:
return
self.friendsList.append(friendId)
@magicWord(category=CATEGORY_SYSTEM_ADMINISTRATOR, types=[str])
def system(message):
"""
Broadcast a <message> to the game server.
"""
message = 'ADMIN: ' + message
dclass = simbase.air.dclassesByName['ClientServicesManager']
dg = dclass.aiFormatUpdate('systemMessage',
OtpDoGlobals.OTP_DO_ID_CLIENT_SERVICES_MANAGER,
10, 1000000, [message])
simbase.air.send(dg)
@magicWord(category=CATEGORY_SYSTEM_ADMINISTRATOR, types=[int])
def maintenance(minutes):
"""
Initiate the maintenance message sequence. It will last for the specified
amount of <minutes>.
"""
def disconnect(task):
dg = PyDatagram()
dg.addServerHeader(10, simbase.air.ourChannel, CLIENTAGENT_EJECT)
dg.addUint16(154)
dg.addString('Toontown Stride is now closed for maintenance.')
simbase.air.send(dg)
return Task.done
def countdown(minutes):
if minutes > 0:
system(OTPLocalizer.CRMaintenanceCountdownMessage % minutes)
else:
system(OTPLocalizer.CRMaintenanceMessage)
taskMgr.doMethodLater(10, disconnect, 'maintenance-disconnection')
if minutes <= 5:
next = 60
minutes -= 1
elif minutes % 5:
next = 60 * (minutes%5)
minutes -= minutes % 5
else:
next = 300
minutes -= 5
if minutes >= 0:
taskMgr.doMethodLater(next, countdown, 'maintenance-task',
extraArgs=[minutes])
countdown(minutes)
@magicWord(category=CATEGORY_SYSTEM_ADMINISTRATOR, types=[str, str])
def accessLevel(accessLevel, storage='PERSISTENT'):
"""
Modify the target's access level.
"""
accessName2Id = {
'user': CATEGORY_USER.defaultAccess,
'u': CATEGORY_USER.defaultAccess,
'communitymanager': CATEGORY_COMMUNITY_MANAGER.defaultAccess,
'community': CATEGORY_COMMUNITY_MANAGER.defaultAccess,
'c': CATEGORY_COMMUNITY_MANAGER.defaultAccess,
'moderator': CATEGORY_MODERATOR.defaultAccess,
'mod': CATEGORY_MODERATOR.defaultAccess,
'm': CATEGORY_MODERATOR.defaultAccess,
'creative': CATEGORY_CREATIVE.defaultAccess,
'creativity': CATEGORY_CREATIVE.defaultAccess,
'c': CATEGORY_CREATIVE.defaultAccess,
'programmer': CATEGORY_PROGRAMMER.defaultAccess,
'coder': CATEGORY_PROGRAMMER.defaultAccess,
'p': CATEGORY_PROGRAMMER.defaultAccess,
'administrator': CATEGORY_ADMINISTRATOR.defaultAccess,
'admin': CATEGORY_ADMINISTRATOR.defaultAccess,
'a': CATEGORY_ADMINISTRATOR.defaultAccess,
'systemadministrator': CATEGORY_SYSTEM_ADMINISTRATOR.defaultAccess,
'systemadmin': CATEGORY_SYSTEM_ADMINISTRATOR.defaultAccess,
'sysadministrator': CATEGORY_SYSTEM_ADMINISTRATOR.defaultAccess,
'sysadmin': CATEGORY_SYSTEM_ADMINISTRATOR.defaultAccess,
'system': CATEGORY_SYSTEM_ADMINISTRATOR.defaultAccess,
'sys': CATEGORY_SYSTEM_ADMINISTRATOR.defaultAccess,
's': CATEGORY_SYSTEM_ADMINISTRATOR.defaultAccess
}
try:
accessLevel = int(accessLevel)
except:
if accessLevel not in accessName2Id:
return 'Invalid access level!'
accessLevel = accessName2Id[accessLevel]
if accessLevel not in accessName2Id.values():
return 'Invalid access level!'
target = spellbook.getTarget()
invoker = spellbook.getInvoker()
if invoker == target:
return "You can't set your own access level!"
if not accessLevel < invoker.getAdminAccess():
return "The target's access level must be lower than yours!"
if target.getAdminAccess() == accessLevel:
return "%s's access level is already %d!" % (target.getName(), accessLevel)
target.b_setAdminAccess(accessLevel)
temporary = storage.upper() in ('SESSION', 'TEMP', 'TEMPORARY')
if not temporary:
target.air.dbInterface.updateObject(
target.air.dbId,
target.getDISLid(),
target.air.dclassesByName['AccountAI'],
{'ADMIN_ACCESS': accessLevel})
if not temporary:
target.d_setSystemMessage(0, '%s set your access level to %d!' % (invoker.getName(), accessLevel))
return "%s's access level has been set to %d." % (target.getName(), accessLevel)
else:
target.d_setSystemMessage(0, '%s set your access level to %d temporarily!' % (invoker.getName(), accessLevel))
return "%s's access level has been set to %d temporarily." % (target.getName(), accessLevel)
@magicWord(category=CATEGORY_COMMUNITY_MANAGER)
def disableGM():
"""
Temporarily disable GM features.
"""
target = spellbook.getTarget()
if hasattr(target, 'oldAccess'):
return 'GM features are already disabled!\nTo enable, use ~enableGM.'
if not target.isAdmin():
return 'Target is not an admin!'
target.oldAccess = target.adminAccess
target.d_setAdminAccess(100)
return 'GM features are disabled!'
@magicWord(category=CATEGORY_COMMUNITY_MANAGER)
def enableGM():
"""
Enable GM features.
"""
target = spellbook.getTarget()
if not hasattr(target, 'oldAccess'):
return 'GM features are not disabled!'
target.d_setAdminAccess(target.oldAccess)
del target.oldAccess
return 'GM features are enabled!'

22
otp/avatar/Emote.py Executable file
View file

@ -0,0 +1,22 @@
from otp.otpbase import OTPLocalizer
import types
class Emote:
EmoteClear = -1
EmoteEnableStateChanged = 'EmoteEnableStateChanged'
def __init__(self):
self.emoteFunc = None
return
def isEnabled(self, index):
if isinstance(index, types.StringType):
index = OTPLocalizer.EmoteFuncDict[index]
if self.emoteFunc == None:
return 0
elif self.emoteFunc[index][1] == 0:
return 1
return 0
globalEmote = None

1177
otp/avatar/LocalAvatar.py Executable file

File diff suppressed because it is too large Load diff

7
otp/avatar/PlayerBase.py Executable file
View file

@ -0,0 +1,7 @@
class PlayerBase:
def atLocation(self, locationId):
return True
def getLocation(self):
return []

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